├── .gitignore ├── README.md ├── example-min.png ├── pom.xml ├── spotify-api-server ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── spotify │ │ │ └── api │ │ │ └── controller │ │ │ ├── Application.java │ │ │ ├── controller │ │ │ └── SearchController.java │ │ │ └── helper │ │ │ ├── CorsFilter.java │ │ │ └── SearchModel.java │ └── resources │ │ └── application.yml │ └── test │ └── java │ └── com │ └── spotify │ └── api │ └── AppTest.java └── spotify-api-ui ├── .gitignore ├── config ├── env.js ├── jest │ ├── cssTransform.js │ └── fileTransform.js ├── paths.js ├── polyfills.js ├── webpack.config.dev.js ├── webpack.config.prod.js └── webpackDevServer.config.js ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── scripts ├── build.js ├── start.js └── test.js ├── sources └── font-awesome │ ├── css │ ├── font-awesome-ie7.css │ ├── font-awesome-ie7.min.css │ ├── font-awesome.css │ └── font-awesome.min.css │ ├── font │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ └── fontawesome-webfont.woff │ ├── less │ ├── bootstrap.less │ ├── core.less │ ├── extras.less │ ├── font-awesome-ie7.less │ ├── font-awesome.less │ ├── icons.less │ ├── mixins.less │ ├── path.less │ └── variables.less │ └── scss │ ├── _bootstrap.scss │ ├── _core.scss │ ├── _extras.scss │ ├── _icons.scss │ ├── _mixins.scss │ ├── _path.scss │ ├── _variables.scss │ ├── font-awesome-ie7.scss │ └── font-awesome.scss └── src ├── app ├── input │ └── Input.js ├── licence │ └── Licence.js ├── limit │ └── Limit.js ├── main │ ├── SpotifyApp.css │ ├── SpotifyApp.js │ └── TableItem.js └── pagination │ └── Pagination.js ├── index.js ├── registerServiceWorker.js └── utils └── RequestUtils.js /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | 18 | ### NetBeans ### 19 | nbproject/private/ 20 | build/ 21 | nbbuild/ 22 | dist/ 23 | nbdist/ 24 | .nb-gradle/ 25 | .gitignore 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SpringBoot - Spotify API - ReactJS 2 | ###### This is a web application developed using Spotify API with React. 3 | ###### There are two modules in the project. 4 | ###### spotify-api-server is a Spring Boot application with using Spotify Search API. 5 | ###### spotify-api-ui is a ReactJS application. 6 | 7 | ![alt tag](./example-min.png) 8 | 9 | ## spotify-api-server 10 | 11 | For use Spotify API you need to take authorization token from Spotify. 12 | You can take authorization token from [Here](https://developer.spotify.com/web-api/console/get-search-item/) 13 | 14 | In the ``` 15 | application.yml 16 | ```, you can set token here. 17 | 18 | This token looks like this; 19 | ``` 20 | token: "BQDYNZJKRPp271pXIiMRrRbx77TSo5BuQKMZeBXFlD9FwwRP4VjbkKxTfwKOvZSS_kZCQYSGK9QA7dKFl63tXo4taPItG6ya0AL-7L2zvOlI8IDrRwoF4yEws8AJjXtf2-PKJqj3hmaz765w_A" 21 | ``` 22 | 23 | The project is a Spring Boot project. For this reason, you can run the Application class by running it. 24 | 25 | ## spotify-api-ui 26 | 27 | ###### This module uses React and React-Bootstrap. Includes paging and limiting features about Spotify response. 28 | 29 | In the project folder ``` 30 | $ cd spotify-api-ui 31 | ``` use the following command. 32 | ``` 33 | $ npm install 34 | ``` 35 | 36 | then 37 | 38 | ``` 39 | $ npm start 40 | ``` 41 | 42 | and project will start at ``` 43 | http://127.0.0.1:3000 44 | ``` 45 | 46 | ## License 47 | 48 | The MIT License (MIT) Copyright (c) 2017 Fatih Totrakanlı -------------------------------------------------------------------------------- /example-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fatihtotrakanli/Spring-Boot-SpotifyAPI-ReactJS/0b10fc426c41dee1f79c9651fb363d9386f6f91f/example-min.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.spotify.api 6 | com.spotify.api 7 | 1.0-SNAPSHOT 8 | 9 | spotify-api-server 10 | 11 | pom 12 | 13 | com.spotify.api 14 | http://maven.apache.org 15 | 16 | 17 | UTF-8 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /spotify-api-server/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 4.0.0 5 | 6 | com.spotify.api 7 | jar 8 | 9 | com.spotify.api 10 | http://maven.apache.org 11 | 12 | 13 | UTF-8 14 | 15 | 16 | 17 | org.springframework.boot 18 | spring-boot-starter-parent 19 | 1.5.2.RELEASE 20 | 21 | 22 | 23 | 24 | 25 | org.apache.httpcomponents 26 | httpclient 27 | 4.5.2 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-web 36 | 37 | 38 | junit 39 | junit 40 | 3.8.1 41 | test 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | org.springframework.boot 50 | spring-boot-maven-plugin 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /spotify-api-server/src/main/java/com/spotify/api/controller/Application.java: -------------------------------------------------------------------------------- 1 | package com.spotify.api.controller; 2 | 3 | import org.springframework.boot.CommandLineRunner; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | import java.io.IOException; 8 | 9 | @SpringBootApplication 10 | public class Application implements CommandLineRunner { 11 | 12 | public static void main(String[] args) throws IOException { 13 | SpringApplication.run(Application.class, args); 14 | } 15 | 16 | @Override 17 | public void run(String... strings) throws Exception { 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /spotify-api-server/src/main/java/com/spotify/api/controller/controller/SearchController.java: -------------------------------------------------------------------------------- 1 | package com.spotify.api.controller.controller; 2 | 3 | import com.spotify.api.controller.helper.SearchModel; 4 | import org.apache.http.HttpResponse; 5 | import org.apache.http.client.HttpClient; 6 | import org.apache.http.client.methods.HttpGet; 7 | import org.apache.http.impl.client.HttpClientBuilder; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.beans.factory.annotation.Value; 11 | import org.springframework.web.bind.annotation.*; 12 | 13 | import java.io.BufferedReader; 14 | import java.io.IOException; 15 | import java.io.InputStreamReader; 16 | 17 | import static org.apache.http.HttpHeaders.USER_AGENT; 18 | 19 | /** 20 | * Created by Fatih Totrakanli on 31/05/2017. 21 | */ 22 | 23 | @RestController 24 | @RequestMapping(value = "search") 25 | public class SearchController { 26 | 27 | private static Logger logger = LoggerFactory.getLogger(SearchController.class.getName()); 28 | 29 | @Value("${token}") 30 | private String token; 31 | 32 | @PostMapping(value = "/api") 33 | public StringBuffer searchQ(@RequestBody SearchModel search) throws IOException { 34 | 35 | String query = search.getQuery().equals("") ? "%2B" : search.getQuery().replaceAll("\\s+", "%2B"); 36 | logger.info(query + " search started..."); 37 | 38 | String url = "https://api.spotify.com/v1/search?q=" + query + "&type=track&limit=" + search.getLimit() + "&offset=" + search.getOffset(); 39 | 40 | HttpClient client = HttpClientBuilder.create().build(); 41 | HttpGet request = new HttpGet(url); 42 | 43 | request.addHeader("User-Agent", USER_AGENT); 44 | request.addHeader("Authorization", "Bearer " + token); 45 | request.addHeader("Accept", "application/json"); 46 | HttpResponse response = client.execute(request); 47 | 48 | logger.info("Response Code : " + response.getStatusLine().getStatusCode()); 49 | 50 | if (response.getStatusLine().getStatusCode() == 401) { 51 | String error = "The server did not respond properly. Please check spotify authorization token from application.yml or get new token from (https://developer.spotify.com/web-api/console/get-search-item/)."; 52 | logger.error(error); 53 | throw new RuntimeException(error); 54 | } else { 55 | if (response.getStatusLine().getStatusCode() != 200) 56 | throw new RuntimeException("Something went wrong."); 57 | } 58 | 59 | BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent())); 60 | 61 | StringBuffer result = new StringBuffer(); 62 | String line = ""; 63 | logger.info("Reading response..."); 64 | while ((line = rd.readLine()) != null) { 65 | result.append(line); 66 | } 67 | 68 | logger.info("Reading response completed successfully."); 69 | 70 | return result; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /spotify-api-server/src/main/java/com/spotify/api/controller/helper/CorsFilter.java: -------------------------------------------------------------------------------- 1 | package com.spotify.api.controller.helper; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import javax.servlet.*; 6 | import javax.servlet.http.HttpServletResponse; 7 | import java.io.IOException; 8 | 9 | /** 10 | * Created by Fatih Totrakanli on 31/05/2017. 11 | */ 12 | 13 | @Component 14 | public class CorsFilter implements Filter { 15 | 16 | 17 | public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { 18 | HttpServletResponse response = (HttpServletResponse) res; 19 | response.setHeader("Access-Control-Allow-Origin", "*"); 20 | response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE"); 21 | response.setHeader("Access-Control-Max-Age", "3600"); 22 | response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type"); 23 | chain.doFilter(req, res); 24 | } 25 | 26 | public void init(FilterConfig filterConfig) { 27 | } 28 | 29 | public void destroy() { 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /spotify-api-server/src/main/java/com/spotify/api/controller/helper/SearchModel.java: -------------------------------------------------------------------------------- 1 | package com.spotify.api.controller.helper; 2 | 3 | /** 4 | * Created by Fatih Totrakanli on 31/05/2017. 5 | */ 6 | public class SearchModel { 7 | 8 | private String query; 9 | 10 | private int limit = 20; 11 | 12 | private int offset = 0; 13 | 14 | public String getQuery() { 15 | return query; 16 | } 17 | 18 | public void setQuery(String query) { 19 | this.query = query; 20 | } 21 | 22 | public int getOffset() { 23 | return offset; 24 | } 25 | 26 | public void setOffset(int offset) { 27 | this.offset = offset; 28 | } 29 | 30 | public int getLimit() { 31 | return limit; 32 | } 33 | 34 | public void setLimit(int limit) { 35 | this.limit = limit; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /spotify-api-server/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | servlet-path: /rest/ 3 | port: 8081 4 | 5 | token: "BQC_DprHnxDYM3SwDFmL3rb63RuWdwmqmq3sEAXnpNYgf0OcWZcusYIC9eqaGk1t1iJJcyzx1j6LQx5usMrm0cK4NDjiF5_tvajqsbTxHDL6z03GPncnksjGMtp7tAaJbw3SBB1wr0vYurAFnQ" -------------------------------------------------------------------------------- /spotify-api-server/src/test/java/com/spotify/api/AppTest.java: -------------------------------------------------------------------------------- 1 | package com.spotify.api; 2 | 3 | import junit.framework.Test; 4 | import junit.framework.TestCase; 5 | import junit.framework.TestSuite; 6 | 7 | /** 8 | * Unit test for simple App. 9 | */ 10 | public class AppTest 11 | extends TestCase 12 | { 13 | /** 14 | * Create the test case 15 | * 16 | * @param testName name of the test case 17 | */ 18 | public AppTest( String testName ) 19 | { 20 | super( testName ); 21 | } 22 | 23 | /** 24 | * @return the suite of tests being tested 25 | */ 26 | public static Test suite() 27 | { 28 | return new TestSuite( AppTest.class ); 29 | } 30 | 31 | /** 32 | * Rigourous Test :-) 33 | */ 34 | public void testApp() 35 | { 36 | assertTrue( true ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /spotify-api-ui/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /spotify-api-ui/config/env.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const paths = require('./paths'); 6 | 7 | // Make sure that including paths.js after env.js will read .env variables. 8 | delete require.cache[require.resolve('./paths')]; 9 | 10 | const NODE_ENV = process.env.NODE_ENV; 11 | if (!NODE_ENV) { 12 | throw new Error( 13 | 'The NODE_ENV environment variable is required but was not specified.' 14 | ); 15 | } 16 | 17 | // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use 18 | var dotenvFiles = [ 19 | `${paths.dotenv}.${NODE_ENV}.local`, 20 | `${paths.dotenv}.${NODE_ENV}`, 21 | // Don't include `.env.local` for `test` environment 22 | // since normally you expect tests to produce the same 23 | // results for everyone 24 | NODE_ENV !== 'test' && `${paths.dotenv}.local`, 25 | paths.dotenv, 26 | ].filter(Boolean); 27 | 28 | // Load environment variables from .env* files. Suppress warnings using silent 29 | // if this file is missing. dotenv will never modify any environment variables 30 | // that have already been set. 31 | // https://github.com/motdotla/dotenv 32 | dotenvFiles.forEach(dotenvFile => { 33 | if (fs.existsSync(dotenvFile)) { 34 | require('dotenv').config({ 35 | path: dotenvFile, 36 | }); 37 | } 38 | }); 39 | 40 | // We support resolving modules according to `NODE_PATH`. 41 | // This lets you use absolute paths in imports inside large monorepos: 42 | // https://github.com/facebookincubator/create-react-app/issues/253. 43 | // It works similar to `NODE_PATH` in Node itself: 44 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 45 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. 46 | // Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. 47 | // https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421 48 | // We also resolve them to make sure all tools using them work consistently. 49 | const appDirectory = fs.realpathSync(process.cwd()); 50 | process.env.NODE_PATH = (process.env.NODE_PATH || '') 51 | .split(path.delimiter) 52 | .filter(folder => folder && !path.isAbsolute(folder)) 53 | .map(folder => path.resolve(appDirectory, folder)) 54 | .join(path.delimiter); 55 | 56 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 57 | // injected into the application via DefinePlugin in Webpack configuration. 58 | const REACT_APP = /^REACT_APP_/i; 59 | 60 | function getClientEnvironment(publicUrl) { 61 | const raw = Object.keys(process.env) 62 | .filter(key => REACT_APP.test(key)) 63 | .reduce( 64 | (env, key) => { 65 | env[key] = process.env[key]; 66 | return env; 67 | }, 68 | { 69 | // Useful for determining whether we’re running in production mode. 70 | // Most importantly, it switches React into the correct mode. 71 | NODE_ENV: process.env.NODE_ENV || 'development', 72 | // Useful for resolving the correct path to static assets in `public`. 73 | // For example, . 74 | // This should only be used as an escape hatch. Normally you would put 75 | // images into the `src` and `import` them in code to get their paths. 76 | PUBLIC_URL: publicUrl, 77 | } 78 | ); 79 | // Stringify all values so we can feed into Webpack DefinePlugin 80 | const stringified = { 81 | 'process.env': Object.keys(raw).reduce( 82 | (env, key) => { 83 | env[key] = JSON.stringify(raw[key]); 84 | return env; 85 | }, 86 | {} 87 | ), 88 | }; 89 | 90 | return { raw, stringified }; 91 | } 92 | 93 | module.exports = getClientEnvironment; 94 | -------------------------------------------------------------------------------- /spotify-api-ui/config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /spotify-api-ui/config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /spotify-api-ui/config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const url = require('url'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebookincubator/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | const envPublicUrl = process.env.PUBLIC_URL; 13 | 14 | function ensureSlash(path, needsSlash) { 15 | const hasSlash = path.endsWith('/'); 16 | if (hasSlash && !needsSlash) { 17 | return path.substr(path, path.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${path}/`; 20 | } else { 21 | return path; 22 | } 23 | } 24 | 25 | const getPublicUrl = appPackageJson => 26 | envPublicUrl || require(appPackageJson).homepage; 27 | 28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 29 | // "public path" at which the app is served. 30 | // Webpack needs to know it to put the right