├── .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 | 
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