diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..6fc8feddf --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +bundle.js \ No newline at end of file diff --git a/__tests__/peepModel.test.js b/__tests__/peepModel.test.js new file mode 100644 index 000000000..673da619f --- /dev/null +++ b/__tests__/peepModel.test.js @@ -0,0 +1,15 @@ +const fs = require('fs') +const PeepsModel = require('../src/peepModel'); + +describe('PeepsModel', () => { + it('initialises with an empty array for Peep objects', () => { + const model = new PeepsModel(); + expect(model.getPeeps()).toEqual([]); + }) + + it('adds a new Peep to the array of Peep objects', () => { + const model = new PeepsModel(); + model.addPeep('first peep'); + expect(model.getPeeps()).toEqual(['first peep']); + }); +}) \ No newline at end of file diff --git a/__tests__/peepsClient.test.js b/__tests__/peepsClient.test.js new file mode 100644 index 000000000..5dc430fbb --- /dev/null +++ b/__tests__/peepsClient.test.js @@ -0,0 +1,64 @@ +const PeepsClient = require('../src/peepsClient'); + +require('jest-fetch-mock').enableMocks(); + +describe('peepsClient', () => { + describe('loadPeeps', () => { + beforeEach(() => { + fetch.resetMocks(); + }); + + it('should return peeps with correct properties', async () => { + const responseData = [ { id: 1, body: 'Hello, world!', created_at: '2022-03-05T12:00:00.000Z', updated_at: '2022-03-05T12:00:00.000Z', user: { id: 1, handle: 'user1' }, likes: [] + } + ]; + + fetch.mockResponseOnce(JSON.stringify(responseData)); + + const peepsClient = new PeepsClient(); + const peeps = await peepsClient.loadPeeps(); + + // check they have the right properties + const peep = peeps[0]; + expect(peep.id).toBeDefined(); + expect(peep.body).toBeDefined(); + expect(peep.created_at).toBeDefined(); + expect(peep.updated_at).toBeDefined(); + expect(peep.user.id).toBeDefined(); + expect(peep.user.handle).toBeDefined(); + expect(Array.isArray(peep.likes)).toBe(true); + if (peep.likes.length > 0) { + const like = peep.likes[0]; + expect(like.user.id).toBeDefined(); + expect(like.user.handle).toBeDefined(); + } + }); + + it('throws an error when fetch fails', async () => { + const fetchError = new Error('Fetch error'); + fetch.mockReject(fetchError); + const client = new PeepsClient(); + + await expect(client.loadPeeps()).rejects.toThrow(fetchError); + }); + }); + + describe('createPeep', () => { + beforeEach(() => { + fetch.resetMocks(); + }); + + it('sends a POST request to the peeps backend to create new peep', async () => { + const client = new PeepsClient(); + + fetch.mockResponseOnce(JSON.stringify({ + name: 'another peep', + id: 456 + })); + + const returnedDataFromApi = await client.createPeep('another peep'); + expect(returnedDataFromApi.name).toEqual('another peep'); + expect(returnedDataFromApi.id).toEqual(456); + }); + }); +}); \ No newline at end of file diff --git a/__tests__/peepsModel.test.js b/__tests__/peepsModel.test.js new file mode 100644 index 000000000..dd216918d --- /dev/null +++ b/__tests__/peepsModel.test.js @@ -0,0 +1,23 @@ +const fs = require('fs') +const PeepsModel = require('../src/peepsModel'); + +describe('PeepsModel', () => { + it('initialises with an empty array for Peep objects', () => { + const model = new PeepsModel(); + expect(model.getPeeps()).toEqual([]); + }) + + it('adds a new Peep to the array of Peep objects', () => { + const model = new PeepsModel(); + model.addPeep('first peep'); + expect(model.getPeeps()).toEqual(['first peep']); + }); + + it('resets the list of peeps', () => { + const peeps = new PeepsModel(); + peeps.addPeep('next peep'); + peeps.reset(); + + expect(peeps.getPeeps()).toEqual([]); + }) +}) \ No newline at end of file diff --git a/__tests__/peepsView.test.js b/__tests__/peepsView.test.js new file mode 100644 index 000000000..3a4261252 --- /dev/null +++ b/__tests__/peepsView.test.js @@ -0,0 +1,92 @@ +require('jest-fetch-mock').enableMocks(); + +const { JSDOM } = require('jsdom'); +const dom = new JSDOM('
'); +global.document = dom.window.document; + +const fs = require('fs'); + +const PeepsView = require('../src/peepsView'); +const PeepsModel = require('../src/peepsModel'); +const PeepsClient = require('../src/peepsClient'); + +jest.mock('../src/peepsClient'); + +describe('Peeps view', () => { + it('displays two peeps', () => { + document.body.innerHTML = fs.readFileSync('./index.html'); + + const model = new PeepsModel; + const view = new PeepsView(model); + model.addPeep('my first peep'); + model.addPeep('my second peep'); + + view.displayPeeps(); + + expect(document.querySelectorAll('div.peep').length).toEqual(2); + }); + + it('allows the user to add a new peep', () => { + document.body.innerHTML = fs.readFileSync('./index.html'); + + const model = new PeepsModel; + const view = new PeepsView(model); + + const input = document.querySelector('#add-peep-input'); + input.value = 'another peep'; + + const button = document.querySelector('#add-peep-btn'); + button.click(); + + expect(document.querySelectorAll('div.peep').length).toEqual(1); + expect(document.querySelectorAll('div.peep')[0].textContent).toEqual('another peep'); + }) + + it('clears previous peeps before displaying', () => { + document.body.innerHTML = fs.readFileSync('./index.html'); + + const model = new PeepsModel; + const view = new PeepsView(model); + + model.addPeep('and another peep'); + model.addPeep('peeping once more'); + + view.displayPeeps(); + view.displayPeeps(); + + expect(document.querySelectorAll('div.peep').length).toEqual(2); + }) + + it('displays peep data from Chitter API', (done) => { + document.body.innerHTML = fs.readFileSync('./index.html'); + PeepsClient.mockClear(); + + const fakeClient = {loadPeeps: () => Promise.resolve(['mock peep'])}; + const client = new PeepsClient(); + const model = new PeepsModel(); + const view = new PeepsView(model, fakeClient); + + view.displayPeepsFromApi().then(() => { + const peepE1s = document.querySelectorAll('.peep'); + expect(peepE1s.length).toEqual(1); + expect(peepE1s[0].textContent).toEqual('mock peep'); + done(); + }); + }); + + it('sends a POST request to create a new peep', () => { + document.body.innerHTML = fs.readFileSync('./index.html'); + PeepsClient.mockClear(); + + const fakeClient = {loadPeeps: () => Promise.resolve(['mock peep'])}; + const client = new PeepsClient(); + const model = new PeepsModel(); + const view = new PeepsView(model, fakeClient); + + view.addNewPeep('mock note') + + const peepE1 = document.querySelectorAll('.peep'); + expect(peepE1.length).toEqual(1); + expect(peepE1[0].textContent).toEqual('mock note'); + }) +}) \ No newline at end of file diff --git a/chitter_api_backend/.gitignore b/chitter_api_backend/.gitignore new file mode 100644 index 000000000..7af6f9454 --- /dev/null +++ b/chitter_api_backend/.gitignore @@ -0,0 +1,26 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore the default SQLite database. +/db/*.sqlite3 +/db/*.sqlite3-journal + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore uploaded files in development +/storage/* + +.byebug_history + +# Ignore master key for decrypting credentials and more. +/config/master.key diff --git a/chitter_api_backend/.rspec b/chitter_api_backend/.rspec new file mode 100644 index 000000000..c99d2e739 --- /dev/null +++ b/chitter_api_backend/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/chitter_api_backend/.ruby-version b/chitter_api_backend/.ruby-version new file mode 100644 index 000000000..9183195ac --- /dev/null +++ b/chitter_api_backend/.ruby-version @@ -0,0 +1 @@ +2.4.0 \ No newline at end of file diff --git a/chitter_api_backend/Gemfile b/chitter_api_backend/Gemfile new file mode 100644 index 000000000..b01931dff --- /dev/null +++ b/chitter_api_backend/Gemfile @@ -0,0 +1,48 @@ +source 'https://rubygems.org' +git_source(:github) { |repo| "https://github.com/#{repo}.git" } + +ruby '2.4.0' + +# Bundle edge Rails instead: gem 'rails', github: 'rails/rails' +gem 'rails', '~> 5.2.0' +# Use PostgreSQL as the database for Active Record +gem 'pg' +# Use Puma as the app server +gem 'puma', '~> 3.11' +# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder +# gem 'jbuilder', '~> 2.5' +# Use Redis adapter to run Action Cable in production +# gem 'redis', '~> 4.0' +# Use ActiveModel has_secure_password +# gem 'bcrypt', '~> 3.1.7' + +# Use ActiveStorage variant +# gem 'mini_magick', '~> 4.8' + +# Use Capistrano for deployment +# gem 'capistrano-rails', group: :development + +# Reduces boot times through caching; required in config/boot.rb +gem 'bootsnap', '>= 1.1.0', require: false + +gem 'bcrypt' + +# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible +gem 'rack-cors' + +group :development, :test do + gem 'rspec-rails' + # Call 'byebug' anywhere in the code to stop execution and get a debugger console + gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] +end + +group :development do + gem 'listen', '>= 3.0.5', '< 3.2' + # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring + gem 'spring' + gem 'spring-watcher-listen', '~> 2.0.0' +end + + +# Windows does not include zoneinfo files, so bundle the tzinfo-data gem +gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] diff --git a/chitter_api_backend/Gemfile.lock b/chitter_api_backend/Gemfile.lock new file mode 100644 index 000000000..d190d5d21 --- /dev/null +++ b/chitter_api_backend/Gemfile.lock @@ -0,0 +1,173 @@ +GEM + remote: https://rubygems.org/ + specs: + actioncable (5.2.4.1) + actionpack (= 5.2.4.1) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + actionmailer (5.2.4.1) + actionpack (= 5.2.4.1) + actionview (= 5.2.4.1) + activejob (= 5.2.4.1) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (5.2.4.1) + actionview (= 5.2.4.1) + activesupport (= 5.2.4.1) + rack (~> 2.0, >= 2.0.8) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.2.4.1) + activesupport (= 5.2.4.1) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (5.2.4.1) + activesupport (= 5.2.4.1) + globalid (>= 0.3.6) + activemodel (5.2.4.1) + activesupport (= 5.2.4.1) + activerecord (5.2.4.1) + activemodel (= 5.2.4.1) + activesupport (= 5.2.4.1) + arel (>= 9.0) + activestorage (5.2.4.1) + actionpack (= 5.2.4.1) + activerecord (= 5.2.4.1) + marcel (~> 0.3.1) + activesupport (5.2.4.1) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + arel (9.0.0) + bcrypt (3.1.13) + bootsnap (1.4.6) + msgpack (~> 1.0) + builder (3.2.4) + byebug (11.1.1) + concurrent-ruby (1.1.6) + crass (1.0.6) + diff-lcs (1.3) + erubi (1.9.0) + ffi (1.12.2) + globalid (0.4.2) + activesupport (>= 4.2.0) + i18n (1.8.2) + concurrent-ruby (~> 1.0) + listen (3.1.5) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + ruby_dep (~> 1.2) + loofah (2.4.0) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + mail (2.7.1) + mini_mime (>= 0.1.1) + marcel (0.3.3) + mimemagic (~> 0.3.2) + method_source (0.9.2) + mimemagic (0.3.4) + mini_mime (1.0.2) + mini_portile2 (2.4.0) + minitest (5.14.0) + msgpack (1.3.3) + nio4r (2.5.2) + nokogiri (1.10.9) + mini_portile2 (~> 2.4.0) + pg (1.2.2) + puma (3.12.4) + rack (2.2.2) + rack-cors (1.1.1) + rack (>= 2.0.0) + rack-test (1.1.0) + rack (>= 1.0, < 3) + rails (5.2.4.1) + actioncable (= 5.2.4.1) + actionmailer (= 5.2.4.1) + actionpack (= 5.2.4.1) + actionview (= 5.2.4.1) + activejob (= 5.2.4.1) + activemodel (= 5.2.4.1) + activerecord (= 5.2.4.1) + activestorage (= 5.2.4.1) + activesupport (= 5.2.4.1) + bundler (>= 1.3.0) + railties (= 5.2.4.1) + sprockets-rails (>= 2.0.0) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.3.0) + loofah (~> 2.3) + railties (5.2.4.1) + actionpack (= 5.2.4.1) + activesupport (= 5.2.4.1) + method_source + rake (>= 0.8.7) + thor (>= 0.19.0, < 2.0) + rake (13.0.1) + rb-fsevent (0.10.3) + rb-inotify (0.10.1) + ffi (~> 1.0) + rspec-core (3.9.1) + rspec-support (~> 3.9.1) + rspec-expectations (3.9.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.9.0) + rspec-mocks (3.9.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.9.0) + rspec-rails (3.9.0) + actionpack (>= 3.0) + activesupport (>= 3.0) + railties (>= 3.0) + rspec-core (~> 3.9.0) + rspec-expectations (~> 3.9.0) + rspec-mocks (~> 3.9.0) + rspec-support (~> 3.9.0) + rspec-support (3.9.2) + ruby_dep (1.5.0) + spring (2.1.0) + spring-watcher-listen (2.0.1) + listen (>= 2.7, < 4.0) + spring (>= 1.2, < 3.0) + sprockets (3.7.2) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.1) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + thor (1.0.1) + thread_safe (0.3.6) + tzinfo (1.2.6) + thread_safe (~> 0.1) + websocket-driver (0.7.1) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.4) + +PLATFORMS + ruby + +DEPENDENCIES + bcrypt + bootsnap (>= 1.1.0) + byebug + listen (>= 3.0.5, < 3.2) + pg + puma (~> 3.11) + rack-cors + rails (~> 5.2.0) + rspec-rails + spring + spring-watcher-listen (~> 2.0.0) + tzinfo-data + +RUBY VERSION + ruby 2.4.0p0 + +BUNDLED WITH + 1.16.1 diff --git a/chitter_api_backend/README.md b/chitter_api_backend/README.md new file mode 100644 index 000000000..ba0cc22ad --- /dev/null +++ b/chitter_api_backend/README.md @@ -0,0 +1,232 @@ +## Chitter Backend + +This is an API backend for a \*witter-like app. It has users, sessions posts, +and likes. + +It is deployed at `https://chitter-backend-api-v2.herokuapp.com/` + +## API docs + +Each of these API endpoints are illustrated by a curl command that you can paste into your terminal to play around with. + +If you ever need more information, pass the `-v` flag to curl in addition to the stated arguments. This will put it into verbose mode, and will show you the HTTP response code and other useful debugging information. + +### Users + +#### POST `/users` + +Creates a new user. + +```bash +curl "https://chitter-backend-api-v2.herokuapp.com/users" \ + -X POST \ + -H "Content-Type: application/json" \ + -d '{"user": {"handle":"kay", "password":"mypassword"}}' +``` + +On success, the above command returns JSON structured like this: + +```json +{ + "id" : 1, + "handle" : "kay" +} +``` + +### Sessions + +#### POST `/sessions` + +Creates a new session, giving you a `user_id` and `session_key` required to perform actions on behalf of the user (e.g. posting peeps, liking peeps). + +Creating a new session renders any previous `session_key`s invalid. + +```bash +curl "https://chitter-backend-api-v2.herokuapp.com/sessions" \ + -X POST \ + -H "Content-Type: application/json" \ + -d '{"session": {"handle":"kay", "password":"mypassword"}}' +``` + +On success, the above command returns JSON structured like this: + +```json +{ + "user_id": 1, + "session_key": "a_valid_session_key" +} +``` + +### Peeps + +#### GET `/peeps` + +Returns a list of the last 50 peeps in reverse chronological order. + +``` +curl "https://chitter-backend-api-v2.herokuapp.com/peeps" +``` + +On success, the above command returns JSON structured like this: + +``` +[ + { + "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" + } + }] + } +] +``` + +#### POST `/peeps` + +Creates a new Peep. + +This endpoint requires a `user_id` and `session_key` given as a token in the authorization header. + +```bash +curl "https://chitter-backend-api-v2.herokuapp.com/peeps" \ + -X POST \ + -H "Authorization: Token token=a_valid_session_key" \ + -H "Content-Type: application/json" \ + -d '{"peep": {"user_id":1, "body":"my first peep :)"}}' +``` + +On success, the above command returns JSON structured like this: + +```json +{ + "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" + } + }] +} +``` + +#### GET `/peeps/:id` + +Returns a single Peep. + +```bash +curl "https://chitter-backend-api-v2.herokuapp.com/peeps/1" +``` + +On success, the above command returns JSON structured like this: + +```json +{ + "id": 1, + "body": "my first peep :)", + "created_at": "2018-06-23T13:12:29.945Z", + "updated_at": "2018-06-23T13:12:29.945Z", + "user": { + "id": 1, + "handle": "kay" + }, + "likes": [] +} +``` + +#### DELETE `/peeps/:id` + +Deletes a Peep. + +This endpoint requires a `user_id` and `session_key` given as a token in the authorization header. + +```bash +curl "https://chitter-backend-api-v2.herokuapp.com/peeps/1" \ + -X DELETE \ + -H "Authorization: Token token=a_valid_session_key" +``` + +The above command returns a `204: No Content` response on success. + +### Likes + +#### PUT `/peeps/:peep_id/likes/:user_id` + +Adds a Like to the Peep by the User. + +This endpoint requires a `user_id` and `session_key` given as a token in the authorization header. + +```bash +curl "https://chitter-backend-api-v2.herokuapp.com/peeps/2/likes/1" \ + -X PUT \ + -H "Authorization: Token token=a_valid_session_key" +``` + +On success, the above command returns JSON structured like this: + +```json +{ + "user": { + "id": 1, + "handle": "kay" + } +} +``` + +#### DELETE `/peeps/:peep_id/likes/:user_id` + +Removes the Like on the Peep by the User. + +This endpoint requires a `user_id` and `session_key` given as a token in the authorization header. + +```bash +curl "https://chitter-backend-api-v2.herokuapp.com/peeps/2/likes/1" \ + -X DELETE \ + -H "Authorization: Token token=a_valid_session_key" +``` + +The above command returns a `204: No Content` response on success. + +### Error responses + +The create/update endpoints return errors of the form: + +```json +{ + "peep": ["handle already taken"] +} +``` + +## API Development Quickstart + +**If you're doing the challenge — you don't need to do this.** + +```bash +$ bundle +$ rspec +$ rails server +``` + +### Heroku + +Auth details for the Makers Heroku account are in 1passward + +#### Logs +```bash +$ heroku logs -a chitter-backend-api-v2 +``` diff --git a/chitter_api_backend/Rakefile b/chitter_api_backend/Rakefile new file mode 100644 index 000000000..e85f91391 --- /dev/null +++ b/chitter_api_backend/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative 'config/application' + +Rails.application.load_tasks diff --git a/chitter_api_backend/app/channels/application_cable/channel.rb b/chitter_api_backend/app/channels/application_cable/channel.rb new file mode 100644 index 000000000..d67269728 --- /dev/null +++ b/chitter_api_backend/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/chitter_api_backend/app/channels/application_cable/connection.rb b/chitter_api_backend/app/channels/application_cable/connection.rb new file mode 100644 index 000000000..0ff5442f4 --- /dev/null +++ b/chitter_api_backend/app/channels/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/chitter_api_backend/app/controllers/application_controller.rb b/chitter_api_backend/app/controllers/application_controller.rb new file mode 100644 index 000000000..4ac8823b0 --- /dev/null +++ b/chitter_api_backend/app/controllers/application_controller.rb @@ -0,0 +1,2 @@ +class ApplicationController < ActionController::API +end diff --git a/chitter_api_backend/app/controllers/concerns/.keep b/chitter_api_backend/app/controllers/concerns/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/chitter_api_backend/app/controllers/likes_controller.rb b/chitter_api_backend/app/controllers/likes_controller.rb new file mode 100644 index 000000000..cccf58b03 --- /dev/null +++ b/chitter_api_backend/app/controllers/likes_controller.rb @@ -0,0 +1,39 @@ +class LikesController < ApplicationController + include ActionController::HttpAuthentication::Token::ControllerMethods + before_action :authorize + + # POST /likes + def update + @like = Like.new(like_params) + + if @like.save + render json: @like, status: :created, location: [@like.peep, @like] + else + render json: @like.errors, status: :unprocessable_entity + end + end + + # DELETE /likes/1 + def destroy + @like = Like.find_by(like_params) + @like.destroy + end + + private + # Only allow a trusted parameter "white list" through. + def like_params + { + user_id: params[:user_id], + peep_id: params[:peep_id] + } + end + + def authorize + authenticate_or_request_with_http_token do |token, options| + next false if like_params[:user_id].blank? + user = User.find(like_params[:user_id]) + next false if user.session_key.blank? + ActiveSupport::SecurityUtils.secure_compare(token, user.session_key) + end + end +end diff --git a/chitter_api_backend/app/controllers/peeps_controller.rb b/chitter_api_backend/app/controllers/peeps_controller.rb new file mode 100644 index 000000000..3c02faf99 --- /dev/null +++ b/chitter_api_backend/app/controllers/peeps_controller.rb @@ -0,0 +1,75 @@ +class PeepsController < ApplicationController + include ActionController::HttpAuthentication::Token::ControllerMethods + before_action :set_peep, only: [:show, :update, :destroy] + before_action :authorize_create, only: [:create] + before_action :authorize_modify, only: [:update, :destroy] + + # GET /peeps + def index + @peeps = Peep.all.order(created_at: :desc).limit(50) + + render json: @peeps + end + + # GET /peeps/1 + def show + render json: @peep + end + + # POST /peeps + def create + @peep = Peep.new(create_peep_params) + + if @peep.save + render json: @peep, status: :created, location: @peep + else + render json: @peep.errors, status: :unprocessable_entity + end + end + + # PATCH/PUT /peeps/1 + def update + if @peep.update(update_peep_params) + render json: @peep + else + render json: @peep.errors, status: :unprocessable_entity + end + end + + # DELETE /peeps/1 + def destroy + @peep.destroy + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_peep + @peep = Peep.find(params[:id]) + end + + # Only allow a trusted parameter "white list" through. + def create_peep_params + params.require(:peep).permit(:user_id, :body) + end + + def update_peep_params + params.require(:peep).permit(:body) + end + + def authorize_create + authenticate_or_request_with_http_token do |token, options| + next false if create_peep_params[:user_id].blank? + user = User.find(create_peep_params[:user_id]) + next false if user.session_key.blank? + ActiveSupport::SecurityUtils.secure_compare(token, user.session_key) + end + end + + def authorize_modify + authenticate_or_request_with_http_token do |token, options| + set_peep + next false if @peep.user.session_key.blank? + ActiveSupport::SecurityUtils.secure_compare(token, @peep.user.session_key) + end + end +end diff --git a/chitter_api_backend/app/controllers/sessions_controller.rb b/chitter_api_backend/app/controllers/sessions_controller.rb new file mode 100644 index 000000000..3896a3a42 --- /dev/null +++ b/chitter_api_backend/app/controllers/sessions_controller.rb @@ -0,0 +1,22 @@ +class SessionsController < ApplicationController + INVALID_AUTH_ERRORS = { password: "Invalid username or password" } + + # POST /sessions + def create + user = User.find_by(handle: params[:session][:handle]) + if user.authenticate(params[:session][:password]) + render json: { + user_id: user.id, + session_key: user.generate_session_key! + }, status: :created + else + render json: { errors: INVALID_AUTH_ERRORS }, status: :unprocessable_entity + end + end + + private + # Only allow a trusted parameter "white list" through. + def session_params + params.require(:session).permit(:handle, :password) + end +end diff --git a/chitter_api_backend/app/controllers/users_controller.rb b/chitter_api_backend/app/controllers/users_controller.rb new file mode 100644 index 000000000..a9c20e3d2 --- /dev/null +++ b/chitter_api_backend/app/controllers/users_controller.rb @@ -0,0 +1,37 @@ +class UsersController < ApplicationController + before_action :set_user, only: [:show] + + # GET /users + def index + @users = User.all + + render json: @users + end + + # GET /users/1 + def show + render json: @user + end + + # POST /users + def create + @user = User.new(user_params) + + if @user.save + render json: @user, status: :created, location: @user + else + render json: @user.errors, status: :unprocessable_entity + end + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_user + @user = User.find(params[:id]) + end + + # Only allow a trusted parameter "white list" through. + def user_params + params.require(:user).permit(:handle, :password) + end +end diff --git a/chitter_api_backend/app/jobs/application_job.rb b/chitter_api_backend/app/jobs/application_job.rb new file mode 100644 index 000000000..a009ace51 --- /dev/null +++ b/chitter_api_backend/app/jobs/application_job.rb @@ -0,0 +1,2 @@ +class ApplicationJob < ActiveJob::Base +end diff --git a/chitter_api_backend/app/mailers/application_mailer.rb b/chitter_api_backend/app/mailers/application_mailer.rb new file mode 100644 index 000000000..286b2239d --- /dev/null +++ b/chitter_api_backend/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: 'from@example.com' + layout 'mailer' +end diff --git a/chitter_api_backend/app/models/application_record.rb b/chitter_api_backend/app/models/application_record.rb new file mode 100644 index 000000000..10a4cba84 --- /dev/null +++ b/chitter_api_backend/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true +end diff --git a/chitter_api_backend/app/models/concerns/.keep b/chitter_api_backend/app/models/concerns/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/chitter_api_backend/app/models/like.rb b/chitter_api_backend/app/models/like.rb new file mode 100644 index 000000000..a40818173 --- /dev/null +++ b/chitter_api_backend/app/models/like.rb @@ -0,0 +1,11 @@ +class Like < ApplicationRecord + belongs_to :user + belongs_to :peep + + validates_presence_of :user, :peep + validates_uniqueness_of :user, scope: :peep + + def as_json(options = {}) + super({ only: [], methods: [:user] }.merge(options)) + end +end diff --git a/chitter_api_backend/app/models/peep.rb b/chitter_api_backend/app/models/peep.rb new file mode 100644 index 000000000..8c5c715ab --- /dev/null +++ b/chitter_api_backend/app/models/peep.rb @@ -0,0 +1,13 @@ +class Peep < ApplicationRecord + belongs_to :user + has_many :likes, dependent: :destroy + + validates_presence_of :user, :body + validates_length_of :body, + maximum: 280, + message: 'Peeps must be under 280 characters.' + + def as_json(options = {}) + super({ except: :user_id, methods: [:user, :likes] }.merge(options)) + end +end diff --git a/chitter_api_backend/app/models/user.rb b/chitter_api_backend/app/models/user.rb new file mode 100644 index 000000000..c4c82f6e6 --- /dev/null +++ b/chitter_api_backend/app/models/user.rb @@ -0,0 +1,27 @@ +class User < ApplicationRecord + has_many :peeps, dependent: :destroy + + validates_presence_of :handle, :password_hash + validates_uniqueness_of :handle + validates_length_of :handle, + maximum: 30, + message: 'Handles must be under 30 characters.' + + def password=(password) + self.password_hash = BCrypt::Password.create(password) + end + + def authenticate(password) + BCrypt::Password.new(password_hash) == password + end + + def generate_session_key! + key = BCrypt::Engine.generate_salt.gsub(/[^a-z0-9]/i, '_') + update!(session_key: key) + key + end + + def as_json(options = {}) + super({ only: [:handle, :id] }.merge(options)) + end +end diff --git a/chitter_api_backend/app/views/layouts/mailer.html.erb b/chitter_api_backend/app/views/layouts/mailer.html.erb new file mode 100644 index 000000000..cbd34d2e9 --- /dev/null +++ b/chitter_api_backend/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ + + + + + + + + + <%= yield %> + + diff --git a/chitter_api_backend/app/views/layouts/mailer.text.erb b/chitter_api_backend/app/views/layouts/mailer.text.erb new file mode 100644 index 000000000..37f0bddbd --- /dev/null +++ b/chitter_api_backend/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/chitter_api_backend/bin/bundle b/chitter_api_backend/bin/bundle new file mode 100755 index 000000000..f19acf5b5 --- /dev/null +++ b/chitter_api_backend/bin/bundle @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) +load Gem.bin_path('bundler', 'bundle') diff --git a/chitter_api_backend/bin/rails b/chitter_api_backend/bin/rails new file mode 100755 index 000000000..5badb2fde --- /dev/null +++ b/chitter_api_backend/bin/rails @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby +begin + load File.expand_path('../spring', __FILE__) +rescue LoadError => e + raise unless e.message.include?('spring') +end +APP_PATH = File.expand_path('../config/application', __dir__) +require_relative '../config/boot' +require 'rails/commands' diff --git a/chitter_api_backend/bin/rake b/chitter_api_backend/bin/rake new file mode 100755 index 000000000..d87d5f578 --- /dev/null +++ b/chitter_api_backend/bin/rake @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby +begin + load File.expand_path('../spring', __FILE__) +rescue LoadError => e + raise unless e.message.include?('spring') +end +require_relative '../config/boot' +require 'rake' +Rake.application.run diff --git a/chitter_api_backend/bin/setup b/chitter_api_backend/bin/setup new file mode 100755 index 000000000..a334d86a6 --- /dev/null +++ b/chitter_api_backend/bin/setup @@ -0,0 +1,33 @@ +#!/usr/bin/env ruby +require 'fileutils' +include FileUtils + +# path to your application root. +APP_ROOT = File.expand_path('..', __dir__) + +def system!(*args) + system(*args) || abort("\n== Command #{args} failed ==") +end + +chdir APP_ROOT do + # This script is a starting point to setup your application. + # Add necessary setup steps to this file. + + puts '== Installing dependencies ==' + system! 'gem install bundler --conservative' + system('bundle check') || system!('bundle install') + + # puts "\n== Copying sample files ==" + # unless File.exist?('config/database.yml') + # cp 'config/database.yml.sample', 'config/database.yml' + # end + + puts "\n== Preparing database ==" + system! 'bin/rails db:setup' + + puts "\n== Removing old logs and tempfiles ==" + system! 'bin/rails log:clear tmp:clear' + + puts "\n== Restarting application server ==" + system! 'bin/rails restart' +end diff --git a/chitter_api_backend/bin/spring b/chitter_api_backend/bin/spring new file mode 100755 index 000000000..fb2ec2ebb --- /dev/null +++ b/chitter_api_backend/bin/spring @@ -0,0 +1,17 @@ +#!/usr/bin/env ruby + +# This file loads spring without using Bundler, in order to be fast. +# It gets overwritten when you run the `spring binstub` command. + +unless defined?(Spring) + require 'rubygems' + require 'bundler' + + lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read) + spring = lockfile.specs.detect { |spec| spec.name == "spring" } + if spring + Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path + gem 'spring', spring.version + require 'spring/binstub' + end +end diff --git a/chitter_api_backend/bin/update b/chitter_api_backend/bin/update new file mode 100755 index 000000000..67d0d4964 --- /dev/null +++ b/chitter_api_backend/bin/update @@ -0,0 +1,28 @@ +#!/usr/bin/env ruby +require 'fileutils' +include FileUtils + +# path to your application root. +APP_ROOT = File.expand_path('..', __dir__) + +def system!(*args) + system(*args) || abort("\n== Command #{args} failed ==") +end + +chdir APP_ROOT do + # This script is a way to update your development environment automatically. + # Add necessary update steps to this file. + + puts '== Installing dependencies ==' + system! 'gem install bundler --conservative' + system('bundle check') || system!('bundle install') + + puts "\n== Updating database ==" + system! 'bin/rails db:migrate' + + puts "\n== Removing old logs and tempfiles ==" + system! 'bin/rails log:clear tmp:clear' + + puts "\n== Restarting application server ==" + system! 'bin/rails restart' +end diff --git a/chitter_api_backend/config.ru b/chitter_api_backend/config.ru new file mode 100644 index 000000000..f7ba0b527 --- /dev/null +++ b/chitter_api_backend/config.ru @@ -0,0 +1,5 @@ +# This file is used by Rack-based servers to start the application. + +require_relative 'config/environment' + +run Rails.application diff --git a/chitter_api_backend/config/application.rb b/chitter_api_backend/config/application.rb new file mode 100644 index 000000000..e636b2100 --- /dev/null +++ b/chitter_api_backend/config/application.rb @@ -0,0 +1,35 @@ +require_relative 'boot' + +require "rails" +# Pick the frameworks you want: +require "active_model/railtie" +require "active_job/railtie" +require "active_record/railtie" +require "active_storage/engine" +require "action_controller/railtie" +require "action_mailer/railtie" +require "action_view/railtie" +require "action_cable/engine" +# require "sprockets/railtie" +require "rails/test_unit/railtie" + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module ChitterBackend + class Application < Rails::Application + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 5.2 + + # Settings in config/environments/* take precedence over those specified here. + # Application configuration can go into files in config/initializers + # -- all .rb files in that directory are automatically loaded after loading + # the framework and any gems in your application. + + # Only loads a smaller set of middleware suitable for API only apps. + # Middleware like session, flash, cookies can be added back manually. + # Skip views, helpers and assets when generating a new resource. + config.api_only = true + end +end diff --git a/chitter_api_backend/config/boot.rb b/chitter_api_backend/config/boot.rb new file mode 100644 index 000000000..b9e460cef --- /dev/null +++ b/chitter_api_backend/config/boot.rb @@ -0,0 +1,4 @@ +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) + +require 'bundler/setup' # Set up gems listed in the Gemfile. +require 'bootsnap/setup' # Speed up boot time by caching expensive operations. diff --git a/chitter_api_backend/config/cable.yml b/chitter_api_backend/config/cable.yml new file mode 100644 index 000000000..c212b3d73 --- /dev/null +++ b/chitter_api_backend/config/cable.yml @@ -0,0 +1,10 @@ +development: + adapter: async + +test: + adapter: async + +production: + adapter: redis + url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + channel_prefix: chitter_backend_production diff --git a/chitter_api_backend/config/credentials.yml.enc b/chitter_api_backend/config/credentials.yml.enc new file mode 100644 index 000000000..34a3d4f81 --- /dev/null +++ b/chitter_api_backend/config/credentials.yml.enc @@ -0,0 +1 @@ +aBkng2IHgRTOYwyh3Nx2492pE64k/wIPVjBuxm8zVnE6Vt4iiSo+jU8hyWp4yz+l08I9rxdsaDxwP4yzwrB7nlVkQBiDIuyUzosGOqTuQYqQXgMSViIQeAMrj23A9M53m7uyO1qBYVLlmqT34glvIrBtXTzMJWpYJYU4nx4jIwU51dhj+TQaZc3scN5XU/g0ibecq/6DzJo7MvG1nEDe6Y+5khGsBRVJtMswlSMJEymBlYIhWNnfp/Y3WZA/hVBBGE7dU5FlvV7SaU/11REaZ1F13bR5x8RvRaDzuruM4vXB+MW7qLWIuqPnWYq1PgT1BDxdpurKPnJ1XvVK07YUYrbrbm31hfuiYfma6L7vx0X1inSJegm09VniE24HSwhfcmvX56kTP+YcYeu9gy0FPTaHaQfhNjkJF0j0--9K6E3lVxQijRQPUp--q230kr7Gb4ojkYoBeX3PLA== \ No newline at end of file diff --git a/chitter_api_backend/config/database.yml b/chitter_api_backend/config/database.yml new file mode 100644 index 000000000..a8a4da7a3 --- /dev/null +++ b/chitter_api_backend/config/database.yml @@ -0,0 +1,25 @@ +# SQLite version 3.x +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem 'sqlite3' +# +default: &default + adapter: postgresql + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + +development: + <<: *default + database: chitter_api_backend_development + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: chitter_api_backend_test + +production: + <<: *default + database: chitter_api_backend_production diff --git a/chitter_api_backend/config/environment.rb b/chitter_api_backend/config/environment.rb new file mode 100644 index 000000000..426333bb4 --- /dev/null +++ b/chitter_api_backend/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative 'application' + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/chitter_api_backend/config/environments/development.rb b/chitter_api_backend/config/environments/development.rb new file mode 100644 index 000000000..d52ec9efb --- /dev/null +++ b/chitter_api_backend/config/environments/development.rb @@ -0,0 +1,54 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # In the development environment your application's code is reloaded on + # every request. This slows down response time but is perfect for development + # since you don't have to restart the web server when you make code changes. + config.cache_classes = false + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable/disable caching. By default caching is disabled. + # Run rails dev:cache to toggle caching. + if Rails.root.join('tmp', 'caching-dev.txt').exist? + config.action_controller.perform_caching = true + + config.cache_store = :memory_store + config.public_file_server.headers = { + 'Cache-Control' => "public, max-age=#{2.days.to_i}" + } + else + config.action_controller.perform_caching = false + + config.cache_store = :null_store + end + + # Store uploaded files on the local file system (see config/storage.yml for options) + config.active_storage.service = :local + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + config.action_mailer.perform_caching = false + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true + + # Use an evented file watcher to asynchronously detect changes in source code, + # routes, locales, etc. This feature depends on the listen gem. + config.file_watcher = ActiveSupport::EventedFileUpdateChecker +end diff --git a/chitter_api_backend/config/environments/production.rb b/chitter_api_backend/config/environments/production.rb new file mode 100644 index 000000000..06aaac39e --- /dev/null +++ b/chitter_api_backend/config/environments/production.rb @@ -0,0 +1,85 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.cache_classes = true + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] + # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). + # config.require_master_key = true + + # Disable serving static files from the `/public` folder by default since + # Apache or NGINX already handles this. + config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.action_controller.asset_host = 'http://assets.example.com' + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + + # Store uploaded files on the local file system (see config/storage.yml for options) + config.active_storage.service = :local + + # Mount Action Cable outside main process or domain + # config.action_cable.mount_path = nil + # config.action_cable.url = 'wss://example.com/cable' + # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + # config.force_ssl = true + + # Use the lowest log level to ensure availability of diagnostic information + # when problems arise. + config.log_level = :debug + + # Prepend all log lines with the following tags. + config.log_tags = [ :request_id ] + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Use a real queuing backend for Active Job (and separate queues per environment) + # config.active_job.queue_adapter = :resque + # config.active_job.queue_name_prefix = "chitter_backend_#{Rails.env}" + + config.action_mailer.perform_caching = false + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners. + config.active_support.deprecation = :notify + + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new + + # Use a different logger for distributed setups. + # require 'syslog/logger' + # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') + + if ENV["RAILS_LOG_TO_STDOUT"].present? + logger = ActiveSupport::Logger.new(STDOUT) + logger.formatter = config.log_formatter + config.logger = ActiveSupport::TaggedLogging.new(logger) + end + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false +end diff --git a/chitter_api_backend/config/environments/test.rb b/chitter_api_backend/config/environments/test.rb new file mode 100644 index 000000000..0a38fd3ce --- /dev/null +++ b/chitter_api_backend/config/environments/test.rb @@ -0,0 +1,46 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # The test environment is used exclusively to run your application's + # test suite. You never need to work with it otherwise. Remember that + # your test database is "scratch space" for the test suite and is wiped + # and recreated between test runs. Don't rely on the data there! + config.cache_classes = true + + # Do not eager load code on boot. This avoids loading your whole application + # just for the purpose of running a single test. If you are using a tool that + # preloads Rails for running tests, you may have to set it to true. + config.eager_load = false + + # Configure public file server for tests with Cache-Control for performance. + config.public_file_server.enabled = true + config.public_file_server.headers = { + 'Cache-Control' => "public, max-age=#{1.hour.to_i}" + } + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Raise exceptions instead of rendering exception templates. + config.action_dispatch.show_exceptions = false + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Store uploaded files on the local file system in a temporary directory + config.active_storage.service = :test + + config.action_mailer.perform_caching = false + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true +end diff --git a/chitter_api_backend/config/initializers/application_controller_renderer.rb b/chitter_api_backend/config/initializers/application_controller_renderer.rb new file mode 100644 index 000000000..89d2efab2 --- /dev/null +++ b/chitter_api_backend/config/initializers/application_controller_renderer.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# ActiveSupport::Reloader.to_prepare do +# ApplicationController.renderer.defaults.merge!( +# http_host: 'example.org', +# https: false +# ) +# end diff --git a/chitter_api_backend/config/initializers/backtrace_silencers.rb b/chitter_api_backend/config/initializers/backtrace_silencers.rb new file mode 100644 index 000000000..59385cdf3 --- /dev/null +++ b/chitter_api_backend/config/initializers/backtrace_silencers.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } + +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. +# Rails.backtrace_cleaner.remove_silencers! diff --git a/chitter_api_backend/config/initializers/cors.rb b/chitter_api_backend/config/initializers/cors.rb new file mode 100644 index 000000000..66b76b513 --- /dev/null +++ b/chitter_api_backend/config/initializers/cors.rb @@ -0,0 +1,15 @@ +# Be sure to restart your server when you modify this file. + +# Avoid CORS issues when API is called from the frontend app. +# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests. + +# Read more: https://github.com/cyu/rack-cors + +Rails.application.config.middleware.insert_before 0, Rack::Cors do + allow do + origins '*' + resource '*', + headers: :any, + methods: [:get, :post, :put, :patch, :delete, :options, :head] + end +end diff --git a/chitter_api_backend/config/initializers/filter_parameter_logging.rb b/chitter_api_backend/config/initializers/filter_parameter_logging.rb new file mode 100644 index 000000000..4a994e1e7 --- /dev/null +++ b/chitter_api_backend/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Configure sensitive parameters which will be filtered from the log file. +Rails.application.config.filter_parameters += [:password] diff --git a/chitter_api_backend/config/initializers/inflections.rb b/chitter_api_backend/config/initializers/inflections.rb new file mode 100644 index 000000000..ac033bf9d --- /dev/null +++ b/chitter_api_backend/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym 'RESTful' +# end diff --git a/chitter_api_backend/config/initializers/mime_types.rb b/chitter_api_backend/config/initializers/mime_types.rb new file mode 100644 index 000000000..dc1899682 --- /dev/null +++ b/chitter_api_backend/config/initializers/mime_types.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf diff --git a/chitter_api_backend/config/initializers/wrap_parameters.rb b/chitter_api_backend/config/initializers/wrap_parameters.rb new file mode 100644 index 000000000..bbfc3961b --- /dev/null +++ b/chitter_api_backend/config/initializers/wrap_parameters.rb @@ -0,0 +1,14 @@ +# Be sure to restart your server when you modify this file. + +# This file contains settings for ActionController::ParamsWrapper which +# is enabled by default. + +# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. +ActiveSupport.on_load(:action_controller) do + wrap_parameters format: [:json] +end + +# To enable root element in JSON for ActiveRecord objects. +# ActiveSupport.on_load(:active_record) do +# self.include_root_in_json = true +# end diff --git a/chitter_api_backend/config/locales/en.yml b/chitter_api_backend/config/locales/en.yml new file mode 100644 index 000000000..decc5a857 --- /dev/null +++ b/chitter_api_backend/config/locales/en.yml @@ -0,0 +1,33 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t 'hello' +# +# In views, this is aliased to just `t`: +# +# <%= t('hello') %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# The following keys must be escaped otherwise they will not be retrieved by +# the default I18n backend: +# +# true, false, on, off, yes, no +# +# Instead, surround them with single quotes. +# +# en: +# 'true': 'foo' +# +# To learn more, please read the Rails Internationalization guide +# available at http://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/chitter_api_backend/config/puma.rb b/chitter_api_backend/config/puma.rb new file mode 100644 index 000000000..a5eccf816 --- /dev/null +++ b/chitter_api_backend/config/puma.rb @@ -0,0 +1,34 @@ +# Puma can serve each request in a thread from an internal thread pool. +# The `threads` method setting takes two numbers: a minimum and maximum. +# Any libraries that use thread pools should be configured to match +# the maximum value specified for Puma. Default is set to 5 threads for minimum +# and maximum; this matches the default thread size of Active Record. +# +threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +# +port ENV.fetch("PORT") { 3000 } + +# Specifies the `environment` that Puma will run in. +# +environment ENV.fetch("RAILS_ENV") { "development" } + +# Specifies the number of `workers` to boot in clustered mode. +# Workers are forked webserver processes. If using threads and workers together +# the concurrency of the application would be max `threads` * `workers`. +# Workers do not work on JRuby or Windows (both of which do not support +# processes). +# +# workers ENV.fetch("WEB_CONCURRENCY") { 2 } + +# Use the `preload_app!` method when specifying a `workers` number. +# This directive tells Puma to first boot the application and load code +# before forking the application. This takes advantage of Copy On Write +# process behavior so workers use less memory. +# +# preload_app! + +# Allow puma to be restarted by `rails restart` command. +plugin :tmp_restart diff --git a/chitter_api_backend/config/routes.rb b/chitter_api_backend/config/routes.rb new file mode 100644 index 000000000..474a6d57d --- /dev/null +++ b/chitter_api_backend/config/routes.rb @@ -0,0 +1,8 @@ +Rails.application.routes.draw do + resources :peeps do + resources :likes, only: [:update, :destroy], param: :user_id + end + resources :users, only: [:index, :show, :create] + resources :sessions, only: [:create] + # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html +end diff --git a/chitter_api_backend/config/spring.rb b/chitter_api_backend/config/spring.rb new file mode 100644 index 000000000..9fa7863f9 --- /dev/null +++ b/chitter_api_backend/config/spring.rb @@ -0,0 +1,6 @@ +%w[ + .ruby-version + .rbenv-vars + tmp/restart.txt + tmp/caching-dev.txt +].each { |path| Spring.watch(path) } diff --git a/chitter_api_backend/config/storage.yml b/chitter_api_backend/config/storage.yml new file mode 100644 index 000000000..d32f76e8f --- /dev/null +++ b/chitter_api_backend/config/storage.yml @@ -0,0 +1,34 @@ +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%= Rails.root.join("storage") %> + +# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) +# amazon: +# service: S3 +# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> +# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> +# region: us-east-1 +# bucket: your_own_bucket + +# Remember not to checkin your GCS keyfile to a repository +# google: +# service: GCS +# project: your_project +# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> +# bucket: your_own_bucket + +# Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# microsoft: +# service: AzureStorage +# storage_account_name: your_account_name +# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> +# container: your_container_name + +# mirror: +# service: Mirror +# primary: local +# mirrors: [ amazon, google, microsoft ] diff --git a/chitter_api_backend/db/migrate/20180621231004_create_users.rb b/chitter_api_backend/db/migrate/20180621231004_create_users.rb new file mode 100644 index 000000000..1ce099c59 --- /dev/null +++ b/chitter_api_backend/db/migrate/20180621231004_create_users.rb @@ -0,0 +1,10 @@ +class CreateUsers < ActiveRecord::Migration[5.2] + def change + create_table :users do |t| + t.string :handle + t.string :password_hash + + t.timestamps + end + end +end diff --git a/chitter_api_backend/db/migrate/20180621231020_create_peeps.rb b/chitter_api_backend/db/migrate/20180621231020_create_peeps.rb new file mode 100644 index 000000000..7a1fe4434 --- /dev/null +++ b/chitter_api_backend/db/migrate/20180621231020_create_peeps.rb @@ -0,0 +1,10 @@ +class CreatePeeps < ActiveRecord::Migration[5.2] + def change + create_table :peeps do |t| + t.belongs_to :user, foreign_key: true + t.text :body + + t.timestamps + end + end +end diff --git a/chitter_api_backend/db/migrate/20180621231031_create_likes.rb b/chitter_api_backend/db/migrate/20180621231031_create_likes.rb new file mode 100644 index 000000000..b6afaa23d --- /dev/null +++ b/chitter_api_backend/db/migrate/20180621231031_create_likes.rb @@ -0,0 +1,10 @@ +class CreateLikes < ActiveRecord::Migration[5.2] + def change + create_table :likes do |t| + t.belongs_to :user, foreign_key: true + t.belongs_to :peep, foreign_key: true + + t.timestamps + end + end +end diff --git a/chitter_api_backend/db/migrate/20180622173432_add_session_key_to_users.rb b/chitter_api_backend/db/migrate/20180622173432_add_session_key_to_users.rb new file mode 100644 index 000000000..932a91f3e --- /dev/null +++ b/chitter_api_backend/db/migrate/20180622173432_add_session_key_to_users.rb @@ -0,0 +1,5 @@ +class AddSessionKeyToUsers < ActiveRecord::Migration[5.2] + def change + add_column :users, :session_key, :string + end +end diff --git a/chitter_api_backend/db/schema.rb b/chitter_api_backend/db/schema.rb new file mode 100644 index 000000000..1e3e260f8 --- /dev/null +++ b/chitter_api_backend/db/schema.rb @@ -0,0 +1,46 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your +# database schema. If you need to create the application database on another +# system, you should be using db:schema:load, not running all the migrations +# from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema.define(version: 2018_06_22_173432) do + + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" + + create_table "likes", force: :cascade do |t| + t.bigint "user_id" + t.bigint "peep_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["peep_id"], name: "index_likes_on_peep_id" + t.index ["user_id"], name: "index_likes_on_user_id" + end + + create_table "peeps", force: :cascade do |t| + t.bigint "user_id" + t.text "body" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["user_id"], name: "index_peeps_on_user_id" + end + + create_table "users", force: :cascade do |t| + t.string "handle" + t.string "password_hash" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "session_key" + end + + add_foreign_key "likes", "peeps" + add_foreign_key "likes", "users" + add_foreign_key "peeps", "users" +end diff --git a/chitter_api_backend/db/seeds.rb b/chitter_api_backend/db/seeds.rb new file mode 100644 index 000000000..1beea2acc --- /dev/null +++ b/chitter_api_backend/db/seeds.rb @@ -0,0 +1,7 @@ +# This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup). +# +# Examples: +# +# movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) +# Character.create(name: 'Luke', movie: movies.first) diff --git a/chitter_api_backend/index.js b/chitter_api_backend/index.js new file mode 100644 index 000000000..359d5af46 --- /dev/null +++ b/chitter_api_backend/index.js @@ -0,0 +1,37 @@ +const express = require('express'); +const cors = require('cors'); +const app = express(); +const PORT = 8080; + +app.use(cors()) + +let peeps = [ + 'This peep is coming from the server' +]; + +app.use(express.json()); + +app.get('/peeps', (req, res) => { + res.json(peeps); +}); + +app.post('/peeps', (req, res) => { + peeps.push(req.body.peep) + res.json(peeps); +}); + +app.listen(PORT); + +const PeepsModel = require('../src/peepsModel'); +const PeepsView = require('../src/peepsView'); +const PeepsClient = require('../src/peepsClient'); + +const client = new PeepsClient(); +const model = new PeepsModel(); +const view = new PeepsView(model, client); + +document.querySelector('#add-peep-btn').addEventListener('click', async () => { + const newPeep = document.querySelector('#add-peep-input').value; + await view.client.createPeep(newPeep); + await view.displayPeepsFromApi(); +}); diff --git a/chitter_api_backend/lib/tasks/.keep b/chitter_api_backend/lib/tasks/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/chitter_api_backend/log/.keep b/chitter_api_backend/log/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/chitter_api_backend/package-lock.json b/chitter_api_backend/package-lock.json new file mode 100644 index 000000000..c13757f0e --- /dev/null +++ b/chitter_api_backend/package-lock.json @@ -0,0 +1,683 @@ +{ + "name": "chitter_api_backend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "chitter_api_backend", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "cors": "^2.8.5", + "express": "^4.18.2" + }, + "devDependencies": { + "jest-fetch-mock": "^3.0.3" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "dev": true, + "dependencies": { + "node-fetch": "2.6.7" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/jest-fetch-mock": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz", + "integrity": "sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==", + "dev": true, + "dependencies": { + "cross-fetch": "^3.0.4", + "promise-polyfill": "^8.1.3" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/promise-polyfill": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz", + "integrity": "sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==", + "dev": true + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } +} diff --git a/chitter_api_backend/package.json b/chitter_api_backend/package.json new file mode 100644 index 000000000..5af251480 --- /dev/null +++ b/chitter_api_backend/package.json @@ -0,0 +1,22 @@ +{ + "name": "chitter_api_backend", + "version": "1.0.0", + "description": "This is an API backend for a \\*witter-like app. It has users, sessions posts, and likes.", + "main": "index.js", + "directories": { + "lib": "lib" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "cors": "^2.8.5", + "express": "^4.18.2" + }, + "devDependencies": { + "jest-fetch-mock": "^3.0.3" + } +} diff --git a/chitter_api_backend/public/robots.txt b/chitter_api_backend/public/robots.txt new file mode 100644 index 000000000..37b576a4a --- /dev/null +++ b/chitter_api_backend/public/robots.txt @@ -0,0 +1 @@ +# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file diff --git a/chitter_api_backend/spec/controllers/likes_controller_spec.rb b/chitter_api_backend/spec/controllers/likes_controller_spec.rb new file mode 100644 index 000000000..1546ec328 --- /dev/null +++ b/chitter_api_backend/spec/controllers/likes_controller_spec.rb @@ -0,0 +1,124 @@ +require 'rails_helper' + +# This spec was generated by rspec-rails when you ran the scaffold generator. +# It demonstrates how one might use RSpec to specify the controller code that +# was generated by Rails when you ran the scaffold generator. +# +# It assumes that the implementation code is generated by the rails scaffold +# generator. If you are using any extension libraries to generate different +# controller code, this generated spec may or may not pass. +# +# It only uses APIs available in rails and/or rspec-rails. There are a number +# of tools you can use to make these specs even more expressive, but we're +# sticking to rails and rspec-rails APIs to keep things simple and stable. +# +# Compared to earlier versions of this generator, there is very limited use of +# stubs and message expectations in this spec. Stubs are only used when there +# is no simpler way to get a handle on the object needed for the example. +# Message expectations are only used when there is no simpler way to specify +# that an instance is receiving a specific message. +# +# Also compared to earlier versions of this generator, there are no longer any +# expectations of assigns and templates rendered. These features have been +# removed from Rails core in Rails 5, but can be added back in via the +# `rails-controller-testing` gem. + +RSpec.describe LikesController, type: :controller do + include AuthorizationHelpers + let(:user) { User.create!(handle: "Kay", password_hash: "gewagewagewa") } + let(:other_user) { User.create!(handle: "Matt", password_hash: "xcvxcvxv") } + let(:peep) { other_user.peeps.create!(body: "Hello!") } + + # This should return the minimal set of attributes required to create a valid + # Like. As you add validations to Like, be sure to + # adjust the attributes here as well. + let(:valid_attributes) { + { user_id: user.id, peep_id: peep.id } + } + + let(:unauthorized_user_attributes) { + { user_id: other_user.id, peep_id: peep.id } + } + + # This should return the minimal set of values that should be in the session + # in order to pass any filters (e.g. authentication) defined in + # LikesController. Be sure to keep this updated too. + let(:valid_session) { {} } + + describe "PUT/PATCH #update" do + context "with valid params" do + it "creates a new Like" do + authorize! + expect { + put :update, params: valid_attributes, session: valid_session + }.to change(Like, :count).by(1) + end + + it "renders a JSON response with the new like" do + authorize! + put :update, params: valid_attributes, session: valid_session + expect(response).to have_http_status(:created) + expect(response.content_type).to eq('application/json') + like = Like.last + expect(response.location).to eq(peep_like_url(like.peep, like)) + end + end + + context "with unauthorized user params" do + it "renders a JSON response with errors for the new like" do + authorize! + put :update, params: unauthorized_user_attributes, session: valid_session + expect(response).to have_http_status(:unauthorized) + end + end + + context "with bad token" do + it "renders a JSON response with errors for the new like" do + authorize_badly! + put :update, params: valid_attributes, session: valid_session + expect(response).to have_http_status(:unauthorized) + end + end + + context "when liking twice" do + it "renders a JSON response with errors for the duplicate user" do + authorize! + put :update, params: valid_attributes, session: valid_session + put :update, params: valid_attributes, session: valid_session + expect(response).to have_http_status(:unprocessable_entity) + expect(response.content_type).to eq('application/json') + end + end + end + + describe "DELETE #destroy" do + it "destroys the requested like" do + authorize! + like = Like.create! valid_attributes + expect { + delete :destroy, params: valid_attributes, session: valid_session + }.to change(Like, :count).by(-1) + end + + context "with unauthorized user params" do + it "renders a JSON response with errors for the new like" do + authorize! + like = Like.create! unauthorized_user_attributes + expect { + delete :destroy, params: unauthorized_user_attributes, session: valid_session + }.not_to change(Like, :count) + end + end + + context "with bad token" do + it "renders a JSON response with errors for the new like" do + authorize_badly! + like = Like.create! valid_attributes + expect { + delete :destroy, params: valid_attributes, session: valid_session + }.not_to change(Like, :count) + end + end + end + +end diff --git a/chitter_api_backend/spec/controllers/peeps_controller_spec.rb b/chitter_api_backend/spec/controllers/peeps_controller_spec.rb new file mode 100644 index 000000000..f1e49aa39 --- /dev/null +++ b/chitter_api_backend/spec/controllers/peeps_controller_spec.rb @@ -0,0 +1,269 @@ +require 'rails_helper' + +# This spec was generated by rspec-rails when you ran the scaffold generator. +# It demonstrates how one might use RSpec to specify the controller code that +# was generated by Rails when you ran the scaffold generator. +# +# It assumes that the implementation code is generated by the rails scaffold +# generator. If you are using any extension libraries to generate different +# controller code, this generated spec may or may not pass. +# +# It only uses APIs available in rails and/or rspec-rails. There are a number +# of tools you can use to make these specs even more expressive, but we're +# sticking to rails and rspec-rails APIs to keep things simple and stable. +# +# Compared to earlier versions of this generator, there is very limited use of +# stubs and message expectations in this spec. Stubs are only used when there +# is no simpler way to get a handle on the object needed for the example. +# Message expectations are only used when there is no simpler way to specify +# that an instance is receiving a specific message. +# +# Also compared to earlier versions of this generator, there are no longer any +# expectations of assigns and templates rendered. These features have been +# removed from Rails core in Rails 5, but can be added back in via the +# `rails-controller-testing` gem. + +RSpec.describe PeepsController, type: :controller do + include AuthorizationHelpers + let(:user) { User.create!(handle: "Kay", password_hash: "gewagewagewa") } + let(:other_user) { User.create!(handle: "Dog", password_hash: "feafewafea") } + + # This should return the minimal set of attributes required to create a valid + # Peep. As you add validations to Peep, be sure to + # adjust the attributes here as well. + let(:valid_attributes) { + { user_id: user.id, body: "Hello!" } + } + + let(:invalid_attributes) { + { user_id: user.id, body: nil } + } + + let(:unauthorized_user_attributes) { + { user_id: other_user.id, body: "world" } + } + + # This should return the minimal set of values that should be in the session + # in order to pass any filters (e.g. authentication) defined in + # PeepsController. Be sure to keep this updated too. + let(:valid_session) { {} } + + describe "GET #index" do + it "returns a JSON list of the peeps in reverse chronological order" do + peep = Peep.create! valid_attributes + peep_2 = Peep.create! valid_attributes.merge(body: "Hello 2") + get :index, params: {}, session: valid_session + expect(response).to be_successful + expect(JSON.parse(response.body)).to eq([ + { + "id" => peep_2.id, + "body" => peep_2.body, + "created_at" => peep_2.created_at.as_json, + "updated_at" => peep_2.updated_at.as_json, + "user" => { + "id" => peep_2.user.id, + "handle" => peep_2.user.handle + }, + "likes" => [] + }, + { + "id" => peep.id, + "body" => peep.body, + "created_at" => peep.created_at.as_json, + "updated_at" => peep.updated_at.as_json, + "user" => { + "id" => peep.user.id, + "handle" => peep.user.handle + }, + "likes" => [] + } + ]) + end + end + + describe "GET #show" do + it "returns JSON of the peep" do + peep = Peep.create! valid_attributes + peep.likes.create!(user: other_user) + get :show, params: {id: peep.to_param}, session: valid_session + expect(response).to be_successful + expect(JSON.parse(response.body)).to eq({ + "id" => peep.id, + "body" => peep.body, + "created_at" => peep.created_at.as_json, + "updated_at" => peep.updated_at.as_json, + "user" => { + "id" => peep.user.id, + "handle" => peep.user.handle + }, + "likes" => [ + { + "user" => { + "id" => other_user.id, + "handle" => "Dog" + } + } + ] + }) + end + end + + describe "POST #create" do + context "with valid params" do + it "creates a new Peep" do + authorize! + expect { + post :create, params: {peep: valid_attributes}, session: valid_session + }.to change(Peep, :count).by(1) + end + + it "renders a JSON response with the new peep" do + authorize! + post :create, params: {peep: valid_attributes}, session: valid_session + expect(response).to have_http_status(:created) + expect(response.content_type).to eq('application/json') + expect(response.location).to eq(peep_url(Peep.last)) + end + end + + context "with invalid params" do + it "renders a JSON response with errors for the new peep" do + authorize! + post :create, params: {peep: invalid_attributes}, session: valid_session + expect(response).to have_http_status(:unprocessable_entity) + expect(response.content_type).to eq('application/json') + end + end + + context "with unauthorised user params" do + it "responds unauthorized" do + authorize_badly! + post :create, params: {peep: unauthorized_user_attributes}, session: valid_session + expect(response).to have_http_status(:unauthorized) + end + + it "does not create the peep" do + authorize! + expect { + post :create, params: {peep: unauthorized_user_attributes}, session: valid_session + }.not_to change(Peep, :count) + end + end + + context "with invalid token" do + it "forbids access" do + authorize_badly! + post :create, params: {peep: valid_attributes}, session: valid_session + expect(response).to have_http_status(:unauthorized) + end + end + end + + describe "PUT #update" do + let(:new_attributes) { + { body: "Woop!" } + } + + context "with valid params" do + it "updates the requested peep" do + authorize! + peep = Peep.create! valid_attributes + + put :update, params: {id: peep.to_param, peep: new_attributes}, session: valid_session + peep.reload + expect(peep.body).to eq new_attributes[:body] + end + + it "renders a JSON response with the peep" do + authorize! + peep = Peep.create! valid_attributes + + put :update, params: {id: peep.to_param, peep: valid_attributes}, session: valid_session + expect(response).to have_http_status(:ok) + expect(response.content_type).to eq('application/json') + end + end + + context "with invalid params" do + it "renders a JSON response with errors for the peep" do + authorize! + peep = Peep.create! valid_attributes + + put :update, params: {id: peep.to_param, peep: invalid_attributes}, session: valid_session + expect(response).to have_http_status(:unprocessable_entity) + expect(response.content_type).to eq('application/json') + end + end + + context "with unauthorised user params" do + it "responds unauthorized" do + authorize! + peep = Peep.create! unauthorized_user_attributes + + put :update, params: {id: peep.to_param, peep: new_attributes}, session: valid_session + expect(response).to have_http_status(:unauthorized) + end + + it "does not update the peep" do + authorize! + peep = Peep.create! unauthorized_user_attributes + + put :update, params: {id: peep.to_param, peep: new_attributes}, session: valid_session + peep.reload + expect(peep.body).to eq unauthorized_user_attributes[:body] + end + end + + context "with invalid token" do + it "responds unauthorized" do + authorize_badly! + peep = Peep.create! valid_attributes + + put :update, params: {id: peep.to_param, peep: new_attributes}, session: valid_session + expect(response).to have_http_status(:unauthorized) + end + + it "does not update the peep" do + authorize_badly! + peep = Peep.create! valid_attributes + + put :update, params: {id: peep.to_param, peep: new_attributes}, session: valid_session + peep.reload + expect(peep.body).to eq valid_attributes[:body] + end + end + end + + describe "DELETE #destroy" do + context "with valid params" do + it "destroys the requested peep" do + authorize! + peep = Peep.create! valid_attributes + expect { + delete :destroy, params: {id: peep.to_param}, session: valid_session + }.to change(Peep, :count).by(-1) + end + end + + context "with unauthorised user params" do + it "does not destroy the requested peep" do + authorize! + peep = Peep.create! unauthorized_user_attributes + expect { + delete :destroy, params: {id: peep.to_param}, session: valid_session + }.not_to change(Peep, :count) + end + end + + context "with invalid token" do + it "destroys the requested peep" do + authorize_badly! + peep = Peep.create! valid_attributes + expect { + delete :destroy, params: {id: peep.to_param}, session: valid_session + }.not_to change(Peep, :count) + end + end + end + +end diff --git a/chitter_api_backend/spec/controllers/sessions_controller_spec.rb b/chitter_api_backend/spec/controllers/sessions_controller_spec.rb new file mode 100644 index 000000000..8c942543c --- /dev/null +++ b/chitter_api_backend/spec/controllers/sessions_controller_spec.rb @@ -0,0 +1,66 @@ +require 'rails_helper' + +# This spec was generated by rspec-rails when you ran the scaffold generator. +# It demonstrates how one might use RSpec to specify the controller code that +# was generated by Rails when you ran the scaffold generator. +# +# It assumes that the implementation code is generated by the rails scaffold +# generator. If you are using any extension libraries to generate different +# controller code, this generated spec may or may not pass. +# +# It only uses APIs available in rails and/or rspec-rails. There are a number +# of tools you can use to make these specs even more expressive, but we're +# sticking to rails and rspec-rails APIs to keep things simple and stable. +# +# Compared to earlier versions of this generator, there is very limited use of +# stubs and message expectations in this spec. Stubs are only used when there +# is no simpler way to get a handle on the object needed for the example. +# Message expectations are only used when there is no simpler way to specify +# that an instance is receiving a specific message. +# +# Also compared to earlier versions of this generator, there are no longer any +# expectations of assigns and templates rendered. These features have been +# removed from Rails core in Rails 5, but can be added back in via the +# `rails-controller-testing` gem. + +RSpec.describe SessionsController, type: :controller do + let(:user) { User.create!(handle: "Kay", password: "hashyhash") } + + # This should return the minimal set of attributes required to create a valid + # Session. As you add validations to Session, be sure to + # adjust the attributes here as well. + let(:valid_attributes) { + { handle: user.handle, password: "hashyhash" } + } + + let(:invalid_attributes) { + { handle: user.handle, password: "hoopyhoop" } + } + + # This should return the minimal set of values that should be in the session + # in order to pass any filters (e.g. authentication) defined in + # SessionsController. Be sure to keep this updated too. + let(:valid_session) { {} } + + describe "POST #create" do + context "with valid params" do + it "renders a JSON response with the new session" do + post :create, params: {session: valid_attributes}, session: valid_session + expect(response).to have_http_status(:created) + expect(response.content_type).to eq('application/json') + expect(JSON.parse(response.body)).to eq( + "session_key" => User.last.session_key, + "user_id" => User.last.id + ) + end + end + + context "with invalid params" do + it "renders a JSON response with errors for the new session" do + post :create, params: {session: invalid_attributes}, session: valid_session + expect(response).to have_http_status(:unprocessable_entity) + expect(response.content_type).to eq('application/json') + end + end + end +end diff --git a/chitter_api_backend/spec/controllers/users_controller_spec.rb b/chitter_api_backend/spec/controllers/users_controller_spec.rb new file mode 100644 index 000000000..9fd9cb43f --- /dev/null +++ b/chitter_api_backend/spec/controllers/users_controller_spec.rb @@ -0,0 +1,99 @@ +require 'rails_helper' + +# This spec was generated by rspec-rails when you ran the scaffold generator. +# It demonstrates how one might use RSpec to specify the controller code that +# was generated by Rails when you ran the scaffold generator. +# +# It assumes that the implementation code is generated by the rails scaffold +# generator. If you are using any extension libraries to generate different +# controller code, this generated spec may or may not pass. +# +# It only uses APIs available in rails and/or rspec-rails. There are a number +# of tools you can use to make these specs even more expressive, but we're +# sticking to rails and rspec-rails APIs to keep things simple and stable. +# +# Compared to earlier versions of this generator, there is very limited use of +# stubs and message expectations in this spec. Stubs are only used when there +# is no simpler way to get a handle on the object needed for the example. +# Message expectations are only used when there is no simpler way to specify +# that an instance is receiving a specific message. +# +# Also compared to earlier versions of this generator, there are no longer any +# expectations of assigns and templates rendered. These features have been +# removed from Rails core in Rails 5, but can be added back in via the +# `rails-controller-testing` gem. + +RSpec.describe UsersController, type: :controller do + + # This should return the minimal set of attributes required to create a valid + # User. As you add validations to User, be sure to + # adjust the attributes here as well. + let(:valid_attributes) { + { handle: "Kay", password: "rockhudson" } + } + + let(:invalid_attributes) { + { handle: nil } + } + + # This should return the minimal set of values that should be in the session + # in order to pass any filters (e.g. authentication) defined in + # UsersController. Be sure to keep this updated too. + let(:valid_session) { {} } + + describe "GET #index" do + it "returns a success response" do + user = User.create! valid_attributes + get :index, params: {}, session: valid_session + expect(response).to be_successful + end + end + + describe "GET #show" do + it "returns a success response" do + user = User.create! valid_attributes + get :show, params: {id: user.to_param}, session: valid_session + expect(response).to be_successful + end + end + + describe "POST #create" do + context "with valid params" do + it "creates a new User" do + expect { + post :create, params: {user: valid_attributes}, session: valid_session + }.to change(User, :count).by(1) + end + + it "renders a JSON response with the new user" do + post :create, params: {user: valid_attributes}, session: valid_session + expect(response).to have_http_status(:created) + expect(response.content_type).to eq('application/json') + user = User.last + expect(JSON.parse(response.body)).to eq({ + "id" => user.id, + "handle" => user.handle + }) + expect(response.location).to eq(user_url(user)) + end + end + + context "with invalid params" do + it "renders a JSON response with errors for the new user" do + post :create, params: {user: invalid_attributes}, session: valid_session + expect(response).to have_http_status(:unprocessable_entity) + expect(response.content_type).to eq('application/json') + end + end + + context "with a duplicate handle" do + it "renders a JSON response with errors for the new user" do + post :create, params: {user: valid_attributes}, session: valid_session + post :create, params: {user: valid_attributes}, session: valid_session + expect(response).to have_http_status(:unprocessable_entity) + expect(response.content_type).to eq('application/json') + end + end + end + +end diff --git a/chitter_api_backend/spec/models/like_spec.rb b/chitter_api_backend/spec/models/like_spec.rb new file mode 100644 index 000000000..a8a1a9465 --- /dev/null +++ b/chitter_api_backend/spec/models/like_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Like, type: :model do + it { is_expected.to be } +end diff --git a/chitter_api_backend/spec/models/peep_spec.rb b/chitter_api_backend/spec/models/peep_spec.rb new file mode 100644 index 000000000..38ec8f92a --- /dev/null +++ b/chitter_api_backend/spec/models/peep_spec.rb @@ -0,0 +1,13 @@ +require 'rails_helper' + +RSpec.describe Peep, type: :model do + let(:user) { User.create!(handle: "Kay", password: "kay") } + it { is_expected.to be } + + it "must have a body of under 280 characters" do + invalid_peep = Peep.new(user: user, body: "a" * 281) + valid_peep = Peep.new(user: user, body: "a" * 280) + expect(invalid_peep).not_to be_valid + expect(valid_peep).to be_valid + end +end diff --git a/chitter_api_backend/spec/models/user_spec.rb b/chitter_api_backend/spec/models/user_spec.rb new file mode 100644 index 000000000..6c86a09f4 --- /dev/null +++ b/chitter_api_backend/spec/models/user_spec.rb @@ -0,0 +1,35 @@ +require 'rails_helper' + +RSpec.describe User, type: :model do + subject(:user) { User.create!(handle: "Kay", password: "rockhudson") } + it { is_expected.to be } + + describe "#authenticate" do + context "with valid details" do + it "authenticates" do + expect(user.authenticate("rockhudson")).to be_truthy + end + end + + context "with invalid details" do + it "does not authenticate" do + expect(user.authenticate("rockhudson")).to be_truthy + end + end + end + + describe "#generate_session_key!" do + it "saves & returns a session key" do + key = user.generate_session_key! + user.reload + expect(user.session_key).to eq key + end + end + + it "must have a handle of under 30 characters" do + invalid_user = User.new(handle: "a" * 31, password: "horse") + valid_user = User.new(handle: "a" * 30, password: "horse") + expect(invalid_user).not_to be_valid + expect(valid_user).to be_valid + end +end diff --git a/chitter_api_backend/spec/rails_helper.rb b/chitter_api_backend/spec/rails_helper.rb new file mode 100644 index 000000000..83bceea6d --- /dev/null +++ b/chitter_api_backend/spec/rails_helper.rb @@ -0,0 +1,59 @@ +# This file is copied to spec/ when you run 'rails generate rspec:install' +require 'spec_helper' +ENV['RAILS_ENV'] ||= 'test' +require File.expand_path('../../config/environment', __FILE__) +# Prevent database truncation if the environment is production +abort("The Rails environment is running in production mode!") if Rails.env.production? +require 'rspec/rails' +# Add additional requires below this line. Rails is not loaded until this point! + +require 'support/authorization_helpers' + +# Requires supporting ruby files with custom matchers and macros, etc, in +# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are +# run as spec files by default. This means that files in spec/support that end +# in _spec.rb will both be required and run as specs, causing the specs to be +# run twice. It is recommended that you do not name files matching this glob to +# end with _spec.rb. You can configure this pattern with the --pattern +# option on the command line or in ~/.rspec, .rspec or `.rspec-local`. +# +# The following line is provided for convenience purposes. It has the downside +# of increasing the boot-up time by auto-requiring all files in the support +# directory. Alternatively, in the individual `*_spec.rb` files, manually +# require only the support files necessary. +# +# Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } + +# Checks for pending migrations and applies them before tests are run. +# If you are not using ActiveRecord, you can remove this line. +ActiveRecord::Migration.maintain_test_schema! + +RSpec.configure do |config| + # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures + config.fixture_path = "#{::Rails.root}/spec/fixtures" + + # If you're not using ActiveRecord, or you'd prefer not to run each of your + # examples within a transaction, remove the following line or assign false + # instead of true. + config.use_transactional_fixtures = true + + # RSpec Rails can automatically mix in different behaviours to your tests + # based on their file location, for example enabling you to call `get` and + # `post` in specs under `spec/controllers`. + # + # You can disable this behaviour by removing the line below, and instead + # explicitly tag your specs with their type, e.g.: + # + # RSpec.describe UsersController, :type => :controller do + # # ... + # end + # + # The different available types are documented in the features, such as in + # https://relishapp.com/rspec/rspec-rails/docs + config.infer_spec_type_from_file_location! + + # Filter lines from Rails gems in backtraces. + config.filter_rails_from_backtrace! + # arbitrary gems may also be filtered via: + # config.filter_gems_from_backtrace("gem name") +end diff --git a/chitter_api_backend/spec/spec_helper.rb b/chitter_api_backend/spec/spec_helper.rb new file mode 100644 index 000000000..ce33d66df --- /dev/null +++ b/chitter_api_backend/spec/spec_helper.rb @@ -0,0 +1,96 @@ +# This file was generated by the `rails generate rspec:install` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/chitter_api_backend/spec/support/authorization_helpers.rb b/chitter_api_backend/spec/support/authorization_helpers.rb new file mode 100644 index 000000000..144040070 --- /dev/null +++ b/chitter_api_backend/spec/support/authorization_helpers.rb @@ -0,0 +1,9 @@ +module AuthorizationHelpers + def authorize! + request.headers["Authorization"] = "Token token=#{user.generate_session_key!}" + end + + def authorize_badly! + request.headers["Authorization"] = "Token token=horse" + end +end diff --git a/chitter_api_backend/tmp/.keep b/chitter_api_backend/tmp/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/chitter_api_backend/vendor/.keep b/chitter_api_backend/vendor/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/index.html b/index.html new file mode 100644 index 000000000..4e4d89879 --- /dev/null +++ b/index.html @@ -0,0 +1,31 @@ + + + + + + + +