Showing
145 changed files
with
4694 additions
and
0 deletions
.gitignore
0 → 100644
.vscode/launch.json
0 → 100644
1 | +{ | ||
2 | + // Use IntelliSense to learn about possible attributes. | ||
3 | + // Hover to view descriptions of existing attributes. | ||
4 | + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 | ||
5 | + "version": "0.2.0", | ||
6 | + "configurations": [ | ||
7 | + { | ||
8 | + "type": "node", | ||
9 | + "request": "launch", | ||
10 | + "name": "Launch Program", | ||
11 | + "program": "${workspaceFolder}/server/src/routers/graphql/Mutation/createPost.ts", | ||
12 | + "outFiles": [ | ||
13 | + "${workspaceFolder}/**/*.js" | ||
14 | + ] | ||
15 | + } | ||
16 | + ] | ||
17 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
.vscode/settings.json
0 → 100644
front/.gitignore
0 → 100644
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* |
front/README.md
0 → 100644
This diff could not be displayed because it is too large.
front/config/env.js
0 → 100644
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. Variable expansion is supported in .env files. | ||
31 | +// https://github.com/motdotla/dotenv | ||
32 | +// https://github.com/motdotla/dotenv-expand | ||
33 | +dotenvFiles.forEach(dotenvFile => { | ||
34 | + if (fs.existsSync(dotenvFile)) { | ||
35 | + require('dotenv-expand')( | ||
36 | + require('dotenv').config({ | ||
37 | + path: dotenvFile, | ||
38 | + }) | ||
39 | + ); | ||
40 | + } | ||
41 | +}); | ||
42 | + | ||
43 | +// We support resolving modules according to `NODE_PATH`. | ||
44 | +// This lets you use absolute paths in imports inside large monorepos: | ||
45 | +// https://github.com/facebookincubator/create-react-app/issues/253. | ||
46 | +// It works similar to `NODE_PATH` in Node itself: | ||
47 | +// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders | ||
48 | +// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. | ||
49 | +// Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. | ||
50 | +// https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421 | ||
51 | +// We also resolve them to make sure all tools using them work consistently. | ||
52 | +const appDirectory = fs.realpathSync(process.cwd()); | ||
53 | +process.env.NODE_PATH = (process.env.NODE_PATH || '') | ||
54 | + .split(path.delimiter) | ||
55 | + .filter(folder => folder && !path.isAbsolute(folder)) | ||
56 | + .map(folder => path.resolve(appDirectory, folder)) | ||
57 | + .join(path.delimiter); | ||
58 | + | ||
59 | +// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be | ||
60 | +// injected into the application via DefinePlugin in Webpack configuration. | ||
61 | +const REACT_APP = /^REACT_APP_/i; | ||
62 | + | ||
63 | +function getClientEnvironment(publicUrl) { | ||
64 | + const raw = Object.keys(process.env) | ||
65 | + .filter(key => REACT_APP.test(key)) | ||
66 | + .reduce( | ||
67 | + (env, key) => { | ||
68 | + env[key] = process.env[key]; | ||
69 | + return env; | ||
70 | + }, | ||
71 | + { | ||
72 | + // Useful for determining whether we’re running in production mode. | ||
73 | + // Most importantly, it switches React into the correct mode. | ||
74 | + NODE_ENV: process.env.NODE_ENV || 'development', | ||
75 | + // Useful for resolving the correct path to static assets in `public`. | ||
76 | + // For example, <img src={process.env.PUBLIC_URL + '/img/logo.png'} />. | ||
77 | + // This should only be used as an escape hatch. Normally you would put | ||
78 | + // images into the `src` and `import` them in code to get their paths. | ||
79 | + PUBLIC_URL: publicUrl, | ||
80 | + } | ||
81 | + ); | ||
82 | + // Stringify all values so we can feed into Webpack DefinePlugin | ||
83 | + const stringified = { | ||
84 | + 'process.env': Object.keys(raw).reduce( | ||
85 | + (env, key) => { | ||
86 | + env[key] = JSON.stringify(raw[key]); | ||
87 | + return env; | ||
88 | + }, | ||
89 | + {} | ||
90 | + ), | ||
91 | + }; | ||
92 | + | ||
93 | + return { raw, stringified }; | ||
94 | +} | ||
95 | + | ||
96 | +module.exports = getClientEnvironment; |
front/config/jest/cssTransform.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +// This is a custom Jest transformer turning style imports into empty objects. | ||
4 | +// http://facebook.github.io/jest/docs/en/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 | +}; |
front/config/jest/fileTransform.js
0 → 100644
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/en/webpack.html | ||
7 | + | ||
8 | +module.exports = { | ||
9 | + process(src, filename) { | ||
10 | + return `module.exports = ${JSON.stringify(path.basename(filename))};`; | ||
11 | + }, | ||
12 | +}; |
front/config/jest/typescriptTransform.js
0 → 100644
front/config/paths.js
0 → 100644
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 <script> hrefs into HTML even in | ||
31 | +// single-page apps that may serve index.html for nested URLs like /todos/42. | ||
32 | +// We can't use a relative path in HTML because we don't want to load something | ||
33 | +// like /todos/42/static/js/bundle.7289d.js. We have to know the root. | ||
34 | +function getServedPath(appPackageJson) { | ||
35 | + const publicUrl = getPublicUrl(appPackageJson); | ||
36 | + const servedUrl = envPublicUrl || | ||
37 | + (publicUrl ? url.parse(publicUrl).pathname : '/'); | ||
38 | + return ensureSlash(servedUrl, true); | ||
39 | +} | ||
40 | + | ||
41 | +// config after eject: we're in ./config/ | ||
42 | +module.exports = { | ||
43 | + dotenv: resolveApp('.env'), | ||
44 | + appBuild: resolveApp('build'), | ||
45 | + appPublic: resolveApp('public'), | ||
46 | + appHtml: resolveApp('public/index.html'), | ||
47 | + appIndexJs: resolveApp('src/index.tsx'), | ||
48 | + appPackageJson: resolveApp('package.json'), | ||
49 | + appSrc: resolveApp('src'), | ||
50 | + yarnLockFile: resolveApp('yarn.lock'), | ||
51 | + testsSetup: resolveApp('src/setupTests.ts'), | ||
52 | + appNodeModules: resolveApp('node_modules'), | ||
53 | + appTsConfig: resolveApp('tsconfig.json'), | ||
54 | + appTsLint: resolveApp('tslint.json'), | ||
55 | + publicUrl: getPublicUrl(resolveApp('package.json')), | ||
56 | + servedPath: getServedPath(resolveApp('package.json')), | ||
57 | +}; |
front/config/polyfills.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +if (typeof Promise === 'undefined') { | ||
4 | + // Rejection tracking prevents a common issue where React gets into an | ||
5 | + // inconsistent state due to an error, but it gets swallowed by a Promise, | ||
6 | + // and the user has no idea what causes React's erratic future behavior. | ||
7 | + require('promise/lib/rejection-tracking').enable(); | ||
8 | + window.Promise = require('promise/lib/es6-extensions.js'); | ||
9 | +} | ||
10 | + | ||
11 | +// fetch() polyfill for making API calls. | ||
12 | +require('whatwg-fetch'); | ||
13 | + | ||
14 | +// Object.assign() is commonly used with React. | ||
15 | +// It will use the native implementation if it's present and isn't buggy. | ||
16 | +Object.assign = require('object-assign'); | ||
17 | + | ||
18 | +// In tests, polyfill requestAnimationFrame since jsdom doesn't provide it yet. | ||
19 | +// We don't polyfill it in the browser--this is user's responsibility. | ||
20 | +if (process.env.NODE_ENV === 'test') { | ||
21 | + require('raf').polyfill(global); | ||
22 | +} |
front/config/webpack.config.dev.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +const autoprefixer = require('autoprefixer'); | ||
4 | +const path = require('path'); | ||
5 | +const webpack = require('webpack'); | ||
6 | +const HtmlWebpackPlugin = require('html-webpack-plugin'); | ||
7 | +const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); | ||
8 | +const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); | ||
9 | +const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin'); | ||
10 | +const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); | ||
11 | +const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); | ||
12 | +const getClientEnvironment = require('./env'); | ||
13 | +const paths = require('./paths'); | ||
14 | +const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); | ||
15 | + | ||
16 | +const publicPath = '/'; | ||
17 | +const publicUrl = ''; | ||
18 | +const env = getClientEnvironment(publicUrl); | ||
19 | + | ||
20 | +module.exports = { | ||
21 | + devtool: 'cheap-module-source-map', | ||
22 | + entry: [ | ||
23 | + require.resolve('./polyfills'), | ||
24 | + require.resolve('react-dev-utils/webpackHotDevClient'), | ||
25 | + paths.appIndexJs, | ||
26 | + ], | ||
27 | + output: { | ||
28 | + pathinfo: true, | ||
29 | + filename: 'static/js/bundle.js', | ||
30 | + chunkFilename: 'static/js/[name].chunk.js', | ||
31 | + publicPath: publicPath, | ||
32 | + devtoolModuleFilenameTemplate: info => | ||
33 | + path.resolve(info.absoluteResourcePath).replace(/\\/g, '/'), | ||
34 | + }, | ||
35 | + resolve: { | ||
36 | + modules: ['src', 'node_modules', paths.appNodeModules].concat( | ||
37 | + process.env.NODE_PATH.split(path.delimiter).filter(Boolean) | ||
38 | + ), | ||
39 | + extensions: [ | ||
40 | + '.mjs', | ||
41 | + '.web.ts', | ||
42 | + '.ts', | ||
43 | + '.web.tsx', | ||
44 | + '.tsx', | ||
45 | + '.web.js', | ||
46 | + '.js', | ||
47 | + '.json', | ||
48 | + '.web.jsx', | ||
49 | + '.jsx', | ||
50 | + ], | ||
51 | + plugins: [ | ||
52 | + new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]), | ||
53 | + new TsconfigPathsPlugin({ configFile: paths.appTsConfig }), | ||
54 | + ], | ||
55 | + }, | ||
56 | + module: { | ||
57 | + strictExportPresence: true, | ||
58 | + rules: [ | ||
59 | + { | ||
60 | + test: /\.(js|jsx|mjs)$/, | ||
61 | + loader: require.resolve('source-map-loader'), | ||
62 | + enforce: 'pre', | ||
63 | + include: paths.appSrc, | ||
64 | + }, | ||
65 | + { | ||
66 | + oneOf: [ | ||
67 | + { | ||
68 | + test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/], | ||
69 | + loader: require.resolve('url-loader'), | ||
70 | + options: { | ||
71 | + limit: 10000, | ||
72 | + name: 'static/media/[name].[hash:8].[ext]', | ||
73 | + }, | ||
74 | + }, | ||
75 | + { | ||
76 | + test: /\.(js|jsx|mjs)$/, | ||
77 | + include: paths.appSrc, | ||
78 | + loader: require.resolve('babel-loader'), | ||
79 | + options: { | ||
80 | + | ||
81 | + compact: true, | ||
82 | + }, | ||
83 | + }, | ||
84 | + { | ||
85 | + test: /\.(ts|tsx)$/, | ||
86 | + include: paths.appSrc, | ||
87 | + use: [ | ||
88 | + { | ||
89 | + loader: require.resolve('ts-loader'), | ||
90 | + options: { | ||
91 | + transpileOnly: true, | ||
92 | + }, | ||
93 | + }, | ||
94 | + ], | ||
95 | + }, | ||
96 | + { | ||
97 | + test: /\.css$/, | ||
98 | + use: [ | ||
99 | + require.resolve('style-loader'), | ||
100 | + { | ||
101 | + loader: require.resolve('css-loader'), | ||
102 | + options: { | ||
103 | + importLoaders: 1, | ||
104 | + }, | ||
105 | + }, | ||
106 | + { | ||
107 | + loader: require.resolve('postcss-loader'), | ||
108 | + options: { | ||
109 | + ident: 'postcss', | ||
110 | + plugins: () => [ | ||
111 | + require('postcss-flexbugs-fixes'), | ||
112 | + autoprefixer({ | ||
113 | + browsers: [ | ||
114 | + '>1%', | ||
115 | + 'last 4 versions', | ||
116 | + 'Firefox ESR', | ||
117 | + 'not ie < 9', // React doesn't support IE8 anyway | ||
118 | + ], | ||
119 | + flexbox: 'no-2009', | ||
120 | + }), | ||
121 | + ], | ||
122 | + }, | ||
123 | + }, | ||
124 | + ], | ||
125 | + }, | ||
126 | + { | ||
127 | + test: /\.scss$/, | ||
128 | + use: [ | ||
129 | + require.resolve('style-loader'), | ||
130 | + { | ||
131 | + loader: require.resolve('css-loader'), | ||
132 | + options: { | ||
133 | + importLoaders: 1, | ||
134 | + }, | ||
135 | + }, | ||
136 | + { | ||
137 | + loader: require.resolve('postcss-loader'), | ||
138 | + options: { | ||
139 | + ident: 'postcss', | ||
140 | + plugins: () => [ | ||
141 | + require('postcss-flexbugs-fixes'), | ||
142 | + autoprefixer({ | ||
143 | + browsers: [ | ||
144 | + '>1%', | ||
145 | + 'last 4 versions', | ||
146 | + 'Firefox ESR', | ||
147 | + 'not ie < 9', // React doesn't support IE8 anyway | ||
148 | + ], | ||
149 | + flexbox: 'no-2009', | ||
150 | + }), | ||
151 | + ], | ||
152 | + }, | ||
153 | + }, | ||
154 | + { | ||
155 | + loader: require.resolve('sass-loader'), | ||
156 | + options: { | ||
157 | + // includePaths: ["absolute/path/a", "absolute/path/b"] | ||
158 | + } | ||
159 | + } | ||
160 | + ], | ||
161 | + }, | ||
162 | + { | ||
163 | + test: /\.(gql|graphql)$/, | ||
164 | + loader: require.resolve('graphql-tag/loader'), | ||
165 | + }, | ||
166 | + { | ||
167 | + exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/], | ||
168 | + loader: require.resolve('file-loader'), | ||
169 | + options: { | ||
170 | + name: 'static/media/[name].[hash:8].[ext]', | ||
171 | + }, | ||
172 | + }, | ||
173 | + ], | ||
174 | + }, | ||
175 | + ], | ||
176 | + }, | ||
177 | + plugins: [ | ||
178 | + new InterpolateHtmlPlugin(env.raw), | ||
179 | + new HtmlWebpackPlugin({ | ||
180 | + inject: true, | ||
181 | + template: paths.appHtml, | ||
182 | + }), | ||
183 | + new webpack.NamedModulesPlugin(), | ||
184 | + new webpack.DefinePlugin(env.stringified), | ||
185 | + new webpack.HotModuleReplacementPlugin(), | ||
186 | + new CaseSensitivePathsPlugin(), | ||
187 | + new WatchMissingNodeModulesPlugin(paths.appNodeModules), | ||
188 | + new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), | ||
189 | + new ForkTsCheckerWebpackPlugin({ | ||
190 | + async: false, | ||
191 | + watch: paths.appSrc, | ||
192 | + tsconfig: paths.appTsConfig, | ||
193 | + tslint: paths.appTsLint, | ||
194 | + }), | ||
195 | + ], | ||
196 | + node: { | ||
197 | + dgram: 'empty', | ||
198 | + fs: 'empty', | ||
199 | + net: 'empty', | ||
200 | + tls: 'empty', | ||
201 | + child_process: 'empty', | ||
202 | + }, | ||
203 | + performance: { | ||
204 | + hints: false, | ||
205 | + }, | ||
206 | +}; |
front/config/webpack.config.prod.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +const autoprefixer = require('autoprefixer'); | ||
4 | +const path = require('path'); | ||
5 | +const webpack = require('webpack'); | ||
6 | +const HtmlWebpackPlugin = require('html-webpack-plugin'); | ||
7 | +const ExtractTextPlugin = require('extract-text-webpack-plugin'); | ||
8 | +const ManifestPlugin = require('webpack-manifest-plugin'); | ||
9 | +const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); | ||
10 | +const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin'); | ||
11 | +const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); | ||
12 | +const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); | ||
13 | +const paths = require('./paths'); | ||
14 | +const getClientEnvironment = require('./env'); | ||
15 | +const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); | ||
16 | +const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); | ||
17 | + | ||
18 | +// Webpack uses `publicPath` to determine where the app is being served from. | ||
19 | +// It requires a trailing slash, or the file assets will get an incorrect path. | ||
20 | +const publicPath = paths.servedPath; | ||
21 | +// Some apps do not use client-side routing with pushState. | ||
22 | +// For these, "homepage" can be set to "." to enable relative asset paths. | ||
23 | +const shouldUseRelativeAssetPaths = publicPath === './'; | ||
24 | +// Source maps are resource heavy and can cause out of memory issue for large source files. | ||
25 | +const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false'; | ||
26 | +// `publicUrl` is just like `publicPath`, but we will provide it to our app | ||
27 | +// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript. | ||
28 | +// Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz. | ||
29 | +const publicUrl = publicPath.slice(0, -1); | ||
30 | +// Get environment variables to inject into our app. | ||
31 | +const env = getClientEnvironment(publicUrl); | ||
32 | + | ||
33 | +// Assert this just to be safe. | ||
34 | +// Development builds of React are slow and not intended for production. | ||
35 | +if (env.stringified['process.env'].NODE_ENV !== '"production"') { | ||
36 | + throw new Error('Production builds must have NODE_ENV=production.'); | ||
37 | +} | ||
38 | + | ||
39 | +// Note: defined here because it will be used more than once. | ||
40 | +const cssFilename = 'static/css/[name].[contenthash:8].css'; | ||
41 | + | ||
42 | +// ExtractTextPlugin expects the build output to be flat. | ||
43 | +// (See https://github.com/webpack-contrib/extract-text-webpack-plugin/issues/27) | ||
44 | +// However, our output is structured with css, js and media folders. | ||
45 | +// To have this structure working with relative paths, we have to use custom options. | ||
46 | +const extractTextPluginOptions = shouldUseRelativeAssetPaths | ||
47 | + ? // Making sure that the publicPath goes back to to build folder. | ||
48 | + { publicPath: Array(cssFilename.split('/').length).join('../') } | ||
49 | + : {}; | ||
50 | + | ||
51 | +// This is the production configuration. | ||
52 | +// It compiles slowly and is focused on producing a fast and minimal bundle. | ||
53 | +// The development configuration is different and lives in a separate file. | ||
54 | +module.exports = { | ||
55 | + // Don't attempt to continue if there are any errors. | ||
56 | + bail: true, | ||
57 | + // We generate sourcemaps in production. This is slow but gives good results. | ||
58 | + // You can exclude the *.map files from the build during deployment. | ||
59 | + devtool: shouldUseSourceMap ? 'source-map' : false, | ||
60 | + // In production, we only want to load the polyfills and the app code. | ||
61 | + entry: [require.resolve('./polyfills'), paths.appIndexJs], | ||
62 | + output: { | ||
63 | + // The build folder. | ||
64 | + path: paths.appBuild, | ||
65 | + // Generated JS file names (with nested folders). | ||
66 | + // There will be one main bundle, and one file per asynchronous chunk. | ||
67 | + // We don't currently advertise code splitting but Webpack supports it. | ||
68 | + filename: 'static/js/[name].[chunkhash:8].js', | ||
69 | + chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js', | ||
70 | + // We inferred the "public path" (such as / or /my-project) from homepage. | ||
71 | + publicPath: publicPath, | ||
72 | + // Point sourcemap entries to original disk location (format as URL on Windows) | ||
73 | + devtoolModuleFilenameTemplate: info => | ||
74 | + path | ||
75 | + .relative(paths.appSrc, info.absoluteResourcePath) | ||
76 | + .replace(/\\/g, '/'), | ||
77 | + }, | ||
78 | + resolve: { | ||
79 | + // This allows you to set a fallback for where Webpack should look for modules. | ||
80 | + // We placed these paths second because we want `node_modules` to "win" | ||
81 | + // if there are any conflicts. This matches Node resolution mechanism. | ||
82 | + // https://github.com/facebookincubator/create-react-app/issues/253 | ||
83 | + modules: ['src', 'node_modules', paths.appNodeModules].concat( | ||
84 | + // It is guaranteed to exist because we tweak it in `env.js` | ||
85 | + process.env.NODE_PATH.split(path.delimiter).filter(Boolean) | ||
86 | + ), | ||
87 | + // These are the reasonable defaults supported by the Node ecosystem. | ||
88 | + // We also include JSX as a common component filename extension to support | ||
89 | + // some tools, although we do not recommend using it, see: | ||
90 | + // https://github.com/facebookincubator/create-react-app/issues/290 | ||
91 | + // `web` extension prefixes have been added for better support | ||
92 | + // for React Native Web. | ||
93 | + extensions: [ | ||
94 | + '.mjs', | ||
95 | + '.web.ts', | ||
96 | + '.ts', | ||
97 | + '.web.tsx', | ||
98 | + '.tsx', | ||
99 | + '.web.js', | ||
100 | + '.js', | ||
101 | + '.json', | ||
102 | + '.web.jsx', | ||
103 | + '.jsx', | ||
104 | + ], | ||
105 | + alias: { | ||
106 | + | ||
107 | + // Support React Native Web | ||
108 | + // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ | ||
109 | + 'react-native': 'react-native-web', | ||
110 | + }, | ||
111 | + plugins: [ | ||
112 | + // Prevents users from importing files from outside of src/ (or node_modules/). | ||
113 | + // This often causes confusion because we only process files within src/ with babel. | ||
114 | + // To fix this, we prevent you from importing files out of src/ -- if you'd like to, | ||
115 | + // please link the files into your node_modules/ and let module-resolution kick in. | ||
116 | + // Make sure your source files are compiled, as they will not be processed in any way. | ||
117 | + new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]), | ||
118 | + new TsconfigPathsPlugin({ configFile: paths.appTsConfig }), | ||
119 | + ], | ||
120 | + }, | ||
121 | + module: { | ||
122 | + strictExportPresence: true, | ||
123 | + rules: [ | ||
124 | + // TODO: Disable require.ensure as it's not a standard language feature. | ||
125 | + // We are waiting for https://github.com/facebookincubator/create-react-app/issues/2176. | ||
126 | + // { parser: { requireEnsure: false } }, | ||
127 | + { | ||
128 | + test: /\.(js|jsx|mjs)$/, | ||
129 | + loader: require.resolve('source-map-loader'), | ||
130 | + enforce: 'pre', | ||
131 | + include: paths.appSrc, | ||
132 | + }, | ||
133 | + { | ||
134 | + // "oneOf" will traverse all following loaders until one will | ||
135 | + // match the requirements. When no loader matches it will fall | ||
136 | + // back to the "file" loader at the end of the loader list. | ||
137 | + oneOf: [ | ||
138 | + // "url" loader works just like "file" loader but it also embeds | ||
139 | + // assets smaller than specified size as data URLs to avoid requests. | ||
140 | + { | ||
141 | + test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/], | ||
142 | + loader: require.resolve('url-loader'), | ||
143 | + options: { | ||
144 | + limit: 10000, | ||
145 | + name: 'static/media/[name].[hash:8].[ext]', | ||
146 | + }, | ||
147 | + }, | ||
148 | + { | ||
149 | + test: /\.(js|jsx|mjs)$/, | ||
150 | + include: paths.appSrc, | ||
151 | + loader: require.resolve('babel-loader'), | ||
152 | + options: { | ||
153 | + | ||
154 | + compact: true, | ||
155 | + }, | ||
156 | + }, | ||
157 | + // Compile .tsx? | ||
158 | + { | ||
159 | + test: /\.(ts|tsx)$/, | ||
160 | + include: paths.appSrc, | ||
161 | + use: [ | ||
162 | + { | ||
163 | + loader: require.resolve('ts-loader'), | ||
164 | + options: { | ||
165 | + // disable type checker - we will use it in fork plugin | ||
166 | + transpileOnly: true, | ||
167 | + }, | ||
168 | + }, | ||
169 | + ], | ||
170 | + }, | ||
171 | + // The notation here is somewhat confusing. | ||
172 | + // "postcss" loader applies autoprefixer to our CSS. | ||
173 | + // "css" loader resolves paths in CSS and adds assets as dependencies. | ||
174 | + // "style" loader normally turns CSS into JS modules injecting <style>, | ||
175 | + // but unlike in development configuration, we do something different. | ||
176 | + // `ExtractTextPlugin` first applies the "postcss" and "css" loaders | ||
177 | + // (second argument), then grabs the result CSS and puts it into a | ||
178 | + // separate file in our build process. This way we actually ship | ||
179 | + // a single CSS file in production instead of JS code injecting <style> | ||
180 | + // tags. If you use code splitting, however, any async bundles will still | ||
181 | + // use the "style" loader inside the async code so CSS from them won't be | ||
182 | + // in the main CSS file. | ||
183 | + { | ||
184 | + test: /\.css$/, | ||
185 | + loader: ExtractTextPlugin.extract( | ||
186 | + Object.assign( | ||
187 | + { | ||
188 | + fallback: { | ||
189 | + loader: require.resolve('style-loader'), | ||
190 | + options: { | ||
191 | + hmr: false, | ||
192 | + }, | ||
193 | + }, | ||
194 | + use: [ | ||
195 | + { | ||
196 | + loader: require.resolve('css-loader'), | ||
197 | + options: { | ||
198 | + importLoaders: 1, | ||
199 | + minimize: true, | ||
200 | + sourceMap: shouldUseSourceMap, | ||
201 | + }, | ||
202 | + }, | ||
203 | + { | ||
204 | + loader: require.resolve('postcss-loader'), | ||
205 | + options: { | ||
206 | + // Necessary for external CSS imports to work | ||
207 | + // https://github.com/facebookincubator/create-react-app/issues/2677 | ||
208 | + ident: 'postcss', | ||
209 | + plugins: () => [ | ||
210 | + require('postcss-flexbugs-fixes'), | ||
211 | + autoprefixer({ | ||
212 | + browsers: [ | ||
213 | + '>1%', | ||
214 | + 'last 4 versions', | ||
215 | + 'Firefox ESR', | ||
216 | + 'not ie < 9', // React doesn't support IE8 anyway | ||
217 | + ], | ||
218 | + flexbox: 'no-2009', | ||
219 | + }), | ||
220 | + ], | ||
221 | + }, | ||
222 | + }, | ||
223 | + ], | ||
224 | + }, | ||
225 | + extractTextPluginOptions | ||
226 | + ) | ||
227 | + ), | ||
228 | + // Note: this won't work without `new ExtractTextPlugin()` in `plugins`. | ||
229 | + }, | ||
230 | + // "file" loader makes sure assets end up in the `build` folder. | ||
231 | + // When you `import` an asset, you get its filename. | ||
232 | + // This loader doesn't use a "test" so it will catch all modules | ||
233 | + // that fall through the other loaders. | ||
234 | + { | ||
235 | + loader: require.resolve('file-loader'), | ||
236 | + // Exclude `js` files to keep "css" loader working as it injects | ||
237 | + // it's runtime that would otherwise processed through "file" loader. | ||
238 | + // Also exclude `html` and `json` extensions so they get processed | ||
239 | + // by webpacks internal loaders. | ||
240 | + exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/], | ||
241 | + options: { | ||
242 | + name: 'static/media/[name].[hash:8].[ext]', | ||
243 | + }, | ||
244 | + }, | ||
245 | + // ** STOP ** Are you adding a new loader? | ||
246 | + // Make sure to add the new loader(s) before the "file" loader. | ||
247 | + ], | ||
248 | + }, | ||
249 | + ], | ||
250 | + }, | ||
251 | + plugins: [ | ||
252 | + // Makes some environment variables available in index.html. | ||
253 | + // The public URL is available as %PUBLIC_URL% in index.html, e.g.: | ||
254 | + // <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> | ||
255 | + // In production, it will be an empty string unless you specify "homepage" | ||
256 | + // in `package.json`, in which case it will be the pathname of that URL. | ||
257 | + new InterpolateHtmlPlugin(env.raw), | ||
258 | + // Generates an `index.html` file with the <script> injected. | ||
259 | + new HtmlWebpackPlugin({ | ||
260 | + inject: true, | ||
261 | + template: paths.appHtml, | ||
262 | + minify: { | ||
263 | + removeComments: true, | ||
264 | + collapseWhitespace: true, | ||
265 | + removeRedundantAttributes: true, | ||
266 | + useShortDoctype: true, | ||
267 | + removeEmptyAttributes: true, | ||
268 | + removeStyleLinkTypeAttributes: true, | ||
269 | + keepClosingSlash: true, | ||
270 | + minifyJS: true, | ||
271 | + minifyCSS: true, | ||
272 | + minifyURLs: true, | ||
273 | + }, | ||
274 | + }), | ||
275 | + // Makes some environment variables available to the JS code, for example: | ||
276 | + // if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`. | ||
277 | + // It is absolutely essential that NODE_ENV was set to production here. | ||
278 | + // Otherwise React will be compiled in the very slow development mode. | ||
279 | + new webpack.DefinePlugin(env.stringified), | ||
280 | + // Minify the code. | ||
281 | + new UglifyJsPlugin({ | ||
282 | + uglifyOptions: { | ||
283 | + parse: { | ||
284 | + // we want uglify-js to parse ecma 8 code. However we want it to output | ||
285 | + // ecma 5 compliant code, to avoid issues with older browsers, this is | ||
286 | + // whey we put `ecma: 5` to the compress and output section | ||
287 | + // https://github.com/facebook/create-react-app/pull/4234 | ||
288 | + ecma: 8, | ||
289 | + }, | ||
290 | + compress: { | ||
291 | + ecma: 5, | ||
292 | + warnings: false, | ||
293 | + // Disabled because of an issue with Uglify breaking seemingly valid code: | ||
294 | + // https://github.com/facebook/create-react-app/issues/2376 | ||
295 | + // Pending further investigation: | ||
296 | + // https://github.com/mishoo/UglifyJS2/issues/2011 | ||
297 | + comparisons: false, | ||
298 | + }, | ||
299 | + mangle: { | ||
300 | + safari10: true, | ||
301 | + }, | ||
302 | + output: { | ||
303 | + ecma: 5, | ||
304 | + comments: false, | ||
305 | + // Turned on because emoji and regex is not minified properly using default | ||
306 | + // https://github.com/facebook/create-react-app/issues/2488 | ||
307 | + ascii_only: true, | ||
308 | + }, | ||
309 | + }, | ||
310 | + // Use multi-process parallel running to improve the build speed | ||
311 | + // Default number of concurrent runs: os.cpus().length - 1 | ||
312 | + parallel: true, | ||
313 | + // Enable file caching | ||
314 | + cache: true, | ||
315 | + sourceMap: shouldUseSourceMap, | ||
316 | + }), // Note: this won't work without ExtractTextPlugin.extract(..) in `loaders`. | ||
317 | + new ExtractTextPlugin({ | ||
318 | + filename: cssFilename, | ||
319 | + }), | ||
320 | + // Generate a manifest file which contains a mapping of all asset filenames | ||
321 | + // to their corresponding output file so that tools can pick it up without | ||
322 | + // having to parse `index.html`. | ||
323 | + new ManifestPlugin({ | ||
324 | + fileName: 'asset-manifest.json', | ||
325 | + }), | ||
326 | + // Generate a service worker script that will precache, and keep up to date, | ||
327 | + // the HTML & assets that are part of the Webpack build. | ||
328 | + new SWPrecacheWebpackPlugin({ | ||
329 | + // By default, a cache-busting query parameter is appended to requests | ||
330 | + // used to populate the caches, to ensure the responses are fresh. | ||
331 | + // If a URL is already hashed by Webpack, then there is no concern | ||
332 | + // about it being stale, and the cache-busting can be skipped. | ||
333 | + dontCacheBustUrlsMatching: /\.\w{8}\./, | ||
334 | + filename: 'service-worker.js', | ||
335 | + logger(message) { | ||
336 | + if (message.indexOf('Total precache size is') === 0) { | ||
337 | + // This message occurs for every build and is a bit too noisy. | ||
338 | + return; | ||
339 | + } | ||
340 | + if (message.indexOf('Skipping static resource') === 0) { | ||
341 | + // This message obscures real errors so we ignore it. | ||
342 | + // https://github.com/facebookincubator/create-react-app/issues/2612 | ||
343 | + return; | ||
344 | + } | ||
345 | + console.log(message); | ||
346 | + }, | ||
347 | + minify: true, | ||
348 | + // For unknown URLs, fallback to the index page | ||
349 | + navigateFallback: publicUrl + '/index.html', | ||
350 | + // Ignores URLs starting from /__ (useful for Firebase): | ||
351 | + // https://github.com/facebookincubator/create-react-app/issues/2237#issuecomment-302693219 | ||
352 | + navigateFallbackWhitelist: [/^(?!\/__).*/], | ||
353 | + // Don't precache sourcemaps (they're large) and build asset manifest: | ||
354 | + staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/], | ||
355 | + }), | ||
356 | + // Moment.js is an extremely popular library that bundles large locale files | ||
357 | + // by default due to how Webpack interprets its code. This is a practical | ||
358 | + // solution that requires the user to opt into importing specific locales. | ||
359 | + // https://github.com/jmblog/how-to-optimize-momentjs-with-webpack | ||
360 | + // You can remove this if you don't use Moment.js: | ||
361 | + new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), | ||
362 | + // Perform type checking and linting in a separate process to speed up compilation | ||
363 | + new ForkTsCheckerWebpackPlugin({ | ||
364 | + async: false, | ||
365 | + tsconfig: paths.appTsConfig, | ||
366 | + tslint: paths.appTsLint, | ||
367 | + }), | ||
368 | + ], | ||
369 | + // Some libraries import Node modules but don't use them in the browser. | ||
370 | + // Tell Webpack to provide empty mocks for them so importing them works. | ||
371 | + node: { | ||
372 | + dgram: 'empty', | ||
373 | + fs: 'empty', | ||
374 | + net: 'empty', | ||
375 | + tls: 'empty', | ||
376 | + child_process: 'empty', | ||
377 | + }, | ||
378 | +}; |
front/config/webpackDevServer.config.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware'); | ||
4 | +const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware'); | ||
5 | +const ignoredFiles = require('react-dev-utils/ignoredFiles'); | ||
6 | +const config = require('./webpack.config.dev'); | ||
7 | +const paths = require('./paths'); | ||
8 | + | ||
9 | +const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; | ||
10 | +const host = process.env.HOST || '0.0.0.0'; | ||
11 | + | ||
12 | +module.exports = function(proxy, allowedHost) { | ||
13 | + return { | ||
14 | + // WebpackDevServer 2.4.3 introduced a security fix that prevents remote | ||
15 | + // websites from potentially accessing local content through DNS rebinding: | ||
16 | + // https://github.com/webpack/webpack-dev-server/issues/887 | ||
17 | + // https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a | ||
18 | + // However, it made several existing use cases such as development in cloud | ||
19 | + // environment or subdomains in development significantly more complicated: | ||
20 | + // https://github.com/facebookincubator/create-react-app/issues/2271 | ||
21 | + // https://github.com/facebookincubator/create-react-app/issues/2233 | ||
22 | + // While we're investigating better solutions, for now we will take a | ||
23 | + // compromise. Since our WDS configuration only serves files in the `public` | ||
24 | + // folder we won't consider accessing them a vulnerability. However, if you | ||
25 | + // use the `proxy` feature, it gets more dangerous because it can expose | ||
26 | + // remote code execution vulnerabilities in backends like Django and Rails. | ||
27 | + // So we will disable the host check normally, but enable it if you have | ||
28 | + // specified the `proxy` setting. Finally, we let you override it if you | ||
29 | + // really know what you're doing with a special environment variable. | ||
30 | + disableHostCheck: !proxy || | ||
31 | + process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true', | ||
32 | + // Enable gzip compression of generated files. | ||
33 | + compress: true, | ||
34 | + // Silence WebpackDevServer's own logs since they're generally not useful. | ||
35 | + // It will still show compile warnings and errors with this setting. | ||
36 | + clientLogLevel: 'none', | ||
37 | + // By default WebpackDevServer serves physical files from current directory | ||
38 | + // in addition to all the virtual build products that it serves from memory. | ||
39 | + // This is confusing because those files won’t automatically be available in | ||
40 | + // production build folder unless we copy them. However, copying the whole | ||
41 | + // project directory is dangerous because we may expose sensitive files. | ||
42 | + // Instead, we establish a convention that only files in `public` directory | ||
43 | + // get served. Our build script will copy `public` into the `build` folder. | ||
44 | + // In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%: | ||
45 | + // <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> | ||
46 | + // In JavaScript code, you can access it with `process.env.PUBLIC_URL`. | ||
47 | + // Note that we only recommend to use `public` folder as an escape hatch | ||
48 | + // for files like `favicon.ico`, `manifest.json`, and libraries that are | ||
49 | + // for some reason broken when imported through Webpack. If you just want to | ||
50 | + // use an image, put it in `src` and `import` it from JavaScript instead. | ||
51 | + contentBase: paths.appPublic, | ||
52 | + // By default files from `contentBase` will not trigger a page reload. | ||
53 | + watchContentBase: true, | ||
54 | + // Enable hot reloading server. It will provide /sockjs-node/ endpoint | ||
55 | + // for the WebpackDevServer client so it can learn when the files were | ||
56 | + // updated. The WebpackDevServer client is included as an entry point | ||
57 | + // in the Webpack development configuration. Note that only changes | ||
58 | + // to CSS are currently hot reloaded. JS changes will refresh the browser. | ||
59 | + hot: true, | ||
60 | + // It is important to tell WebpackDevServer to use the same "root" path | ||
61 | + // as we specified in the config. In development, we always serve from /. | ||
62 | + publicPath: config.output.publicPath, | ||
63 | + // WebpackDevServer is noisy by default so we emit custom message instead | ||
64 | + // by listening to the compiler events with `compiler.plugin` calls above. | ||
65 | + quiet: true, | ||
66 | + // Reportedly, this avoids CPU overload on some systems. | ||
67 | + // https://github.com/facebookincubator/create-react-app/issues/293 | ||
68 | + // src/node_modules is not ignored to support absolute imports | ||
69 | + // https://github.com/facebookincubator/create-react-app/issues/1065 | ||
70 | + watchOptions: { | ||
71 | + ignored: ignoredFiles(paths.appSrc), | ||
72 | + }, | ||
73 | + // Enable HTTPS if the HTTPS environment variable is set to 'true' | ||
74 | + https: protocol === 'https', | ||
75 | + host: host, | ||
76 | + overlay: false, | ||
77 | + historyApiFallback: { | ||
78 | + // Paths with dots should still use the history fallback. | ||
79 | + // See https://github.com/facebookincubator/create-react-app/issues/387. | ||
80 | + disableDotRule: true, | ||
81 | + }, | ||
82 | + public: allowedHost, | ||
83 | + proxy, | ||
84 | + before(app) { | ||
85 | + // This lets us open files from the runtime error overlay. | ||
86 | + app.use(errorOverlayMiddleware()); | ||
87 | + // This service worker file is effectively a 'no-op' that will reset any | ||
88 | + // previous service worker registered for the same host:port combination. | ||
89 | + // We do this in development to avoid hitting the production cache if | ||
90 | + // it used the same host and port. | ||
91 | + // https://github.com/facebookincubator/create-react-app/issues/2272#issuecomment-302832432 | ||
92 | + app.use(noopServiceWorkerMiddleware()); | ||
93 | + }, | ||
94 | + }; | ||
95 | +}; |
front/images.d.ts
0 → 100644
front/package.json
0 → 100644
1 | +{ | ||
2 | + "name": "front", | ||
3 | + "version": "0.1.0", | ||
4 | + "private": true, | ||
5 | + "proxy": "http://0.0.0.0:7777", | ||
6 | + "dependencies": { | ||
7 | + "apollo-boost": "^0.1.4", | ||
8 | + "apollo-cache-inmemory": "^1.2.1", | ||
9 | + "apollo-link-context": "^1.0.8", | ||
10 | + "apollo-link-http": "^1.5.4", | ||
11 | + "apollo-upload-client": "^8.0.0", | ||
12 | + "axios": "^0.18.0", | ||
13 | + "graphql": "^0.13.2", | ||
14 | + "material-ui": "^0.20.0", | ||
15 | + "ramda": "^0.25.0", | ||
16 | + "react": "^16.3.1", | ||
17 | + "react-apollo": "^2.1.3", | ||
18 | + "react-dom": "^16.3.1", | ||
19 | + "react-redux": "^5.0.7", | ||
20 | + "react-router-dom": "^4.2.2", | ||
21 | + "redux": "^4.0.0", | ||
22 | + "redux-actions": "^2.3.0", | ||
23 | + "redux-logger": "^3.0.6", | ||
24 | + "redux-observable": "^1.0.0-alpha.2", | ||
25 | + "rxjs": "^6.1.0" | ||
26 | + }, | ||
27 | + "scripts": { | ||
28 | + "start": "node scripts/start.js", | ||
29 | + "build": "node scripts/build.js", | ||
30 | + "test": "node scripts/test.js --env=jsdom" | ||
31 | + }, | ||
32 | + "devDependencies": { | ||
33 | + "@types/graphql": "^0.13.0", | ||
34 | + "@types/jest": "^22.2.3", | ||
35 | + "@types/material-ui": "^0.21.2", | ||
36 | + "@types/node": "^9.6.5", | ||
37 | + "@types/ramda": "^0.25.21", | ||
38 | + "@types/react": "^16.3.10", | ||
39 | + "@types/react-dom": "^16.0.5", | ||
40 | + "@types/react-redux": "^5.0.19", | ||
41 | + "@types/react-router-dom": "^4.2.6", | ||
42 | + "@types/redux-actions": "^2.2.4", | ||
43 | + "@types/redux-logger": "^3.0.6", | ||
44 | + "async-each": "^1.0.1", | ||
45 | + "autoprefixer": "7.1.6", | ||
46 | + "babel-jest": "^22.1.0", | ||
47 | + "babel-loader": "^7.1.2", | ||
48 | + "babel-preset-react-app": "^3.1.1", | ||
49 | + "case-sensitive-paths-webpack-plugin": "2.1.1", | ||
50 | + "chalk": "1.1.3", | ||
51 | + "css-loader": "0.28.7", | ||
52 | + "dotenv": "4.0.0", | ||
53 | + "dotenv-expand": "4.2.0", | ||
54 | + "extract-text-webpack-plugin": "3.0.2", | ||
55 | + "file-loader": "0.11.2", | ||
56 | + "fork-ts-checker-webpack-plugin": "^0.2.8", | ||
57 | + "fs-extra": "3.0.1", | ||
58 | + "html-webpack-plugin": "2.29.0", | ||
59 | + "jest": "22.1.4", | ||
60 | + "node-sass": "^4.9.0", | ||
61 | + "object-assign": "4.1.1", | ||
62 | + "postcss-flexbugs-fixes": "3.2.0", | ||
63 | + "postcss-loader": "2.0.8", | ||
64 | + "promise": "8.0.1", | ||
65 | + "raf": "3.4.0", | ||
66 | + "react-dev-utils": "^5.0.1", | ||
67 | + "resolve": "1.6.0", | ||
68 | + "sass-loader": "^7.0.1", | ||
69 | + "source-map-loader": "^0.2.1", | ||
70 | + "style-loader": "0.19.0", | ||
71 | + "styled-components": "^3.2.5", | ||
72 | + "sw-precache-webpack-plugin": "0.11.4", | ||
73 | + "ts-jest": "22.0.1", | ||
74 | + "ts-loader": "^2.3.7", | ||
75 | + "tsconfig-paths-webpack-plugin": "^2.0.0", | ||
76 | + "tslint": "^5.7.0", | ||
77 | + "tslint-config-prettier": "^1.10.0", | ||
78 | + "tslint-react": "^3.2.0", | ||
79 | + "typescript": "^2.8.1", | ||
80 | + "uglifyjs-webpack-plugin": "^1.1.8", | ||
81 | + "url-loader": "0.6.2", | ||
82 | + "webpack": "3.8.1", | ||
83 | + "webpack-dev-server": "2.9.4", | ||
84 | + "webpack-manifest-plugin": "1.3.2", | ||
85 | + "whatwg-fetch": "2.0.3" | ||
86 | + }, | ||
87 | + "jest": { | ||
88 | + "collectCoverageFrom": [ | ||
89 | + "src/**/*.{js,jsx,ts,tsx}" | ||
90 | + ], | ||
91 | + "setupFiles": [ | ||
92 | + "<rootDir>/config/polyfills.js" | ||
93 | + ], | ||
94 | + "testMatch": [ | ||
95 | + "<rootDir>/src/**/__tests__/**/*.(j|t)s?(x)", | ||
96 | + "<rootDir>/src/**/?(*.)(spec|test).(j|t)s?(x)" | ||
97 | + ], | ||
98 | + "testEnvironment": "node", | ||
99 | + "testURL": "http://localhost", | ||
100 | + "transform": { | ||
101 | + "^.+\\.(js|jsx|mjs)$": "<rootDir>/node_modules/babel-jest", | ||
102 | + "^.+\\.tsx?$": "<rootDir>/config/jest/typescriptTransform.js", | ||
103 | + "^.+\\.css$": "<rootDir>/config/jest/cssTransform.js", | ||
104 | + "^(?!.*\\.(js|jsx|mjs|css|json)$)": "<rootDir>/config/jest/fileTransform.js" | ||
105 | + }, | ||
106 | + "transformIgnorePatterns": [ | ||
107 | + "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|ts|tsx)$" | ||
108 | + ], | ||
109 | + "moduleNameMapper": { | ||
110 | + "^react-native$": "react-native-web" | ||
111 | + }, | ||
112 | + "moduleFileExtensions": [ | ||
113 | + "web.ts", | ||
114 | + "ts", | ||
115 | + "web.tsx", | ||
116 | + "tsx", | ||
117 | + "web.js", | ||
118 | + "js", | ||
119 | + "web.jsx", | ||
120 | + "jsx", | ||
121 | + "json", | ||
122 | + "node", | ||
123 | + "mjs" | ||
124 | + ], | ||
125 | + "globals": { | ||
126 | + "ts-jest": { | ||
127 | + "tsConfigFile": "/home/merong/Project/money/front/tsconfig.test.json" | ||
128 | + } | ||
129 | + } | ||
130 | + }, | ||
131 | + "babel": { | ||
132 | + "presets": [ | ||
133 | + "react-app" | ||
134 | + ] | ||
135 | + }, | ||
136 | + "eslintConfig": { | ||
137 | + "extends": "react-app" | ||
138 | + } | ||
139 | +} |
front/public/favicon.ico
0 → 100644
No preview for this file type
front/public/index.html
0 → 100644
1 | +<!DOCTYPE html> | ||
2 | +<html lang="en"> | ||
3 | + <head> | ||
4 | + <meta charset="utf-8"> | ||
5 | + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> | ||
6 | + <meta name="theme-color" content="#000000"> | ||
7 | + <!-- | ||
8 | + manifest.json provides metadata used when your web app is added to the | ||
9 | + homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/ | ||
10 | + --> | ||
11 | + <link rel="manifest" href="%PUBLIC_URL%/manifest.json"> | ||
12 | + <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> | ||
13 | + <!-- | ||
14 | + Notice the use of %PUBLIC_URL% in the tags above. | ||
15 | + It will be replaced with the URL of the `public` folder during the build. | ||
16 | + Only files inside the `public` folder can be referenced from the HTML. | ||
17 | + | ||
18 | + Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will | ||
19 | + work correctly both with client-side routing and a non-root public URL. | ||
20 | + Learn how to configure a non-root public URL by running `npm run build`. | ||
21 | + --> | ||
22 | + <title>React App</title> | ||
23 | + </head> | ||
24 | + <body> | ||
25 | + <noscript> | ||
26 | + You need to enable JavaScript to run this app. | ||
27 | + </noscript> | ||
28 | + <div id="root"></div> | ||
29 | + <!-- | ||
30 | + This HTML file is a template. | ||
31 | + If you open it directly in the browser, you will see an empty page. | ||
32 | + | ||
33 | + You can add webfonts, meta tags, or analytics to this file. | ||
34 | + The build step will place the bundled scripts into the <body> tag. | ||
35 | + | ||
36 | + To begin the development, run `npm start` or `yarn start`. | ||
37 | + To create a production bundle, use `npm run build` or `yarn build`. | ||
38 | + --> | ||
39 | + </body> | ||
40 | +</html> |
front/public/manifest.json
0 → 100644
1 | +{ | ||
2 | + "short_name": "React App", | ||
3 | + "name": "Create React App Sample", | ||
4 | + "icons": [ | ||
5 | + { | ||
6 | + "src": "favicon.ico", | ||
7 | + "sizes": "64x64 32x32 24x24 16x16", | ||
8 | + "type": "image/x-icon" | ||
9 | + } | ||
10 | + ], | ||
11 | + "start_url": "./index.html", | ||
12 | + "display": "standalone", | ||
13 | + "theme_color": "#000000", | ||
14 | + "background_color": "#ffffff" | ||
15 | +} |
front/scripts/build.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +// Do this as the first thing so that any code reading it knows the right env. | ||
4 | +process.env.BABEL_ENV = 'production'; | ||
5 | +process.env.NODE_ENV = 'production'; | ||
6 | + | ||
7 | +// Makes the script crash on unhandled rejections instead of silently | ||
8 | +// ignoring them. In the future, promise rejections that are not handled will | ||
9 | +// terminate the Node.js process with a non-zero exit code. | ||
10 | +process.on('unhandledRejection', err => { | ||
11 | + throw err; | ||
12 | +}); | ||
13 | + | ||
14 | +// Ensure environment variables are read. | ||
15 | +require('../config/env'); | ||
16 | + | ||
17 | +const path = require('path'); | ||
18 | +const chalk = require('chalk'); | ||
19 | +const fs = require('fs-extra'); | ||
20 | +const webpack = require('webpack'); | ||
21 | +const config = require('../config/webpack.config.prod'); | ||
22 | +const paths = require('../config/paths'); | ||
23 | +const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); | ||
24 | +const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); | ||
25 | +const printHostingInstructions = require('react-dev-utils/printHostingInstructions'); | ||
26 | +const FileSizeReporter = require('react-dev-utils/FileSizeReporter'); | ||
27 | +const printBuildError = require('react-dev-utils/printBuildError'); | ||
28 | + | ||
29 | +const measureFileSizesBeforeBuild = FileSizeReporter.measureFileSizesBeforeBuild; | ||
30 | +const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild; | ||
31 | +const useYarn = fs.existsSync(paths.yarnLockFile); | ||
32 | + | ||
33 | +// These sizes are pretty large. We'll warn for bundles exceeding them. | ||
34 | +const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024; | ||
35 | +const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024; | ||
36 | + | ||
37 | +// Warn and crash if required files are missing | ||
38 | +if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { | ||
39 | + process.exit(1); | ||
40 | +} | ||
41 | + | ||
42 | +// First, read the current file sizes in build directory. | ||
43 | +// This lets us display how much they changed later. | ||
44 | +measureFileSizesBeforeBuild(paths.appBuild) | ||
45 | + .then(previousFileSizes => { | ||
46 | + // Remove all content but keep the directory so that | ||
47 | + // if you're in it, you don't end up in Trash | ||
48 | + fs.emptyDirSync(paths.appBuild); | ||
49 | + // Merge with the public folder | ||
50 | + copyPublicFolder(); | ||
51 | + // Start the webpack build | ||
52 | + return build(previousFileSizes); | ||
53 | + }) | ||
54 | + .then( | ||
55 | + ({ stats, previousFileSizes, warnings }) => { | ||
56 | + if (warnings.length) { | ||
57 | + console.log(chalk.yellow('Compiled with warnings.\n')); | ||
58 | + console.log(warnings.join('\n\n')); | ||
59 | + console.log( | ||
60 | + '\nSearch for the ' + | ||
61 | + chalk.underline(chalk.yellow('keywords')) + | ||
62 | + ' to learn more about each warning.' | ||
63 | + ); | ||
64 | + console.log( | ||
65 | + 'To ignore, add ' + | ||
66 | + chalk.cyan('// eslint-disable-next-line') + | ||
67 | + ' to the line before.\n' | ||
68 | + ); | ||
69 | + } else { | ||
70 | + console.log(chalk.green('Compiled successfully.\n')); | ||
71 | + } | ||
72 | + | ||
73 | + console.log('File sizes after gzip:\n'); | ||
74 | + printFileSizesAfterBuild( | ||
75 | + stats, | ||
76 | + previousFileSizes, | ||
77 | + paths.appBuild, | ||
78 | + WARN_AFTER_BUNDLE_GZIP_SIZE, | ||
79 | + WARN_AFTER_CHUNK_GZIP_SIZE | ||
80 | + ); | ||
81 | + console.log(); | ||
82 | + | ||
83 | + const appPackage = require(paths.appPackageJson); | ||
84 | + const publicUrl = paths.publicUrl; | ||
85 | + const publicPath = config.output.publicPath; | ||
86 | + const buildFolder = path.relative(process.cwd(), paths.appBuild); | ||
87 | + printHostingInstructions( | ||
88 | + appPackage, | ||
89 | + publicUrl, | ||
90 | + publicPath, | ||
91 | + buildFolder, | ||
92 | + useYarn | ||
93 | + ); | ||
94 | + }, | ||
95 | + err => { | ||
96 | + console.log(chalk.red('Failed to compile.\n')); | ||
97 | + printBuildError(err); | ||
98 | + process.exit(1); | ||
99 | + } | ||
100 | + ); | ||
101 | + | ||
102 | +// Create the production build and print the deployment instructions. | ||
103 | +function build(previousFileSizes) { | ||
104 | + console.log('Creating an optimized production build...'); | ||
105 | + | ||
106 | + let compiler = webpack(config); | ||
107 | + return new Promise((resolve, reject) => { | ||
108 | + compiler.run((err, stats) => { | ||
109 | + if (err) { | ||
110 | + return reject(err); | ||
111 | + } | ||
112 | + const messages = formatWebpackMessages(stats.toJson({}, true)); | ||
113 | + if (messages.errors.length) { | ||
114 | + // Only keep the first error. Others are often indicative | ||
115 | + // of the same problem, but confuse the reader with noise. | ||
116 | + if (messages.errors.length > 1) { | ||
117 | + messages.errors.length = 1; | ||
118 | + } | ||
119 | + return reject(new Error(messages.errors.join('\n\n'))); | ||
120 | + } | ||
121 | + if ( | ||
122 | + process.env.CI && | ||
123 | + (typeof process.env.CI !== 'string' || | ||
124 | + process.env.CI.toLowerCase() !== 'false') && | ||
125 | + messages.warnings.length | ||
126 | + ) { | ||
127 | + console.log( | ||
128 | + chalk.yellow( | ||
129 | + '\nTreating warnings as errors because process.env.CI = true.\n' + | ||
130 | + 'Most CI servers set it automatically.\n' | ||
131 | + ) | ||
132 | + ); | ||
133 | + return reject(new Error(messages.warnings.join('\n\n'))); | ||
134 | + } | ||
135 | + return resolve({ | ||
136 | + stats, | ||
137 | + previousFileSizes, | ||
138 | + warnings: messages.warnings, | ||
139 | + }); | ||
140 | + }); | ||
141 | + }); | ||
142 | +} | ||
143 | + | ||
144 | +function copyPublicFolder() { | ||
145 | + fs.copySync(paths.appPublic, paths.appBuild, { | ||
146 | + dereference: true, | ||
147 | + filter: file => file !== paths.appHtml, | ||
148 | + }); | ||
149 | +} |
front/scripts/start.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +// Do this as the first thing so that any code reading it knows the right env. | ||
4 | +process.env.BABEL_ENV = 'development'; | ||
5 | +process.env.NODE_ENV = 'development'; | ||
6 | + | ||
7 | +// Makes the script crash on unhandled rejections instead of silently | ||
8 | +// ignoring them. In the future, promise rejections that are not handled will | ||
9 | +// terminate the Node.js process with a non-zero exit code. | ||
10 | +process.on('unhandledRejection', err => { | ||
11 | + throw err; | ||
12 | +}); | ||
13 | + | ||
14 | +// Ensure environment variables are read. | ||
15 | +require('../config/env'); | ||
16 | + | ||
17 | +const fs = require('fs'); | ||
18 | +const chalk = require('chalk'); | ||
19 | +const webpack = require('webpack'); | ||
20 | +const WebpackDevServer = require('webpack-dev-server'); | ||
21 | +const clearConsole = require('react-dev-utils/clearConsole'); | ||
22 | +const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); | ||
23 | +const { | ||
24 | + choosePort, | ||
25 | + createCompiler, | ||
26 | + prepareProxy, | ||
27 | + prepareUrls, | ||
28 | +} = require('react-dev-utils/WebpackDevServerUtils'); | ||
29 | +const openBrowser = require('react-dev-utils/openBrowser'); | ||
30 | +const paths = require('../config/paths'); | ||
31 | +const config = require('../config/webpack.config.dev'); | ||
32 | +const createDevServerConfig = require('../config/webpackDevServer.config'); | ||
33 | + | ||
34 | +const useYarn = fs.existsSync(paths.yarnLockFile); | ||
35 | +const isInteractive = process.stdout.isTTY; | ||
36 | + | ||
37 | +// Warn and crash if required files are missing | ||
38 | +if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { | ||
39 | + process.exit(1); | ||
40 | +} | ||
41 | + | ||
42 | +// Tools like Cloud9 rely on this. | ||
43 | +const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000; | ||
44 | +const HOST = process.env.HOST || '0.0.0.0'; | ||
45 | + | ||
46 | +if (process.env.HOST) { | ||
47 | + console.log( | ||
48 | + chalk.cyan( | ||
49 | + `Attempting to bind to HOST environment variable: ${chalk.yellow( | ||
50 | + chalk.bold(process.env.HOST) | ||
51 | + )}` | ||
52 | + ) | ||
53 | + ); | ||
54 | + console.log( | ||
55 | + `If this was unintentional, check that you haven't mistakenly set it in your shell.` | ||
56 | + ); | ||
57 | + console.log(`Learn more here: ${chalk.yellow('http://bit.ly/2mwWSwH')}`); | ||
58 | + console.log(); | ||
59 | +} | ||
60 | + | ||
61 | +// We attempt to use the default port but if it is busy, we offer the user to | ||
62 | +// run on a different port. `choosePort()` Promise resolves to the next free port. | ||
63 | +choosePort(HOST, DEFAULT_PORT) | ||
64 | + .then(port => { | ||
65 | + if (port == null) { | ||
66 | + // We have not found a port. | ||
67 | + return; | ||
68 | + } | ||
69 | + const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; | ||
70 | + const appName = require(paths.appPackageJson).name; | ||
71 | + const urls = prepareUrls(protocol, HOST, port); | ||
72 | + // Create a webpack compiler that is configured with custom messages. | ||
73 | + const compiler = createCompiler(webpack, config, appName, urls, useYarn); | ||
74 | + // Load proxy config | ||
75 | + const proxySetting = require(paths.appPackageJson).proxy; | ||
76 | + const proxyConfig = prepareProxy(proxySetting, paths.appPublic); | ||
77 | + // Serve webpack assets generated by the compiler over a web sever. | ||
78 | + const serverConfig = createDevServerConfig( | ||
79 | + proxyConfig, | ||
80 | + urls.lanUrlForConfig | ||
81 | + ); | ||
82 | + const devServer = new WebpackDevServer(compiler, serverConfig); | ||
83 | + // Launch WebpackDevServer. | ||
84 | + devServer.listen(port, HOST, err => { | ||
85 | + if (err) { | ||
86 | + return console.log(err); | ||
87 | + } | ||
88 | + if (isInteractive) { | ||
89 | + clearConsole(); | ||
90 | + } | ||
91 | + console.log(chalk.cyan('Starting the development server...\n')); | ||
92 | + }); | ||
93 | + | ||
94 | + ['SIGINT', 'SIGTERM'].forEach(function(sig) { | ||
95 | + process.on(sig, function() { | ||
96 | + devServer.close(); | ||
97 | + process.exit(); | ||
98 | + }); | ||
99 | + }); | ||
100 | + }) | ||
101 | + .catch(err => { | ||
102 | + if (err && err.message) { | ||
103 | + console.log(err.message); | ||
104 | + } | ||
105 | + process.exit(1); | ||
106 | + }); |
front/scripts/test.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +// Do this as the first thing so that any code reading it knows the right env. | ||
4 | +process.env.BABEL_ENV = 'test'; | ||
5 | +process.env.NODE_ENV = 'test'; | ||
6 | +process.env.PUBLIC_URL = ''; | ||
7 | + | ||
8 | +// Makes the script crash on unhandled rejections instead of silently | ||
9 | +// ignoring them. In the future, promise rejections that are not handled will | ||
10 | +// terminate the Node.js process with a non-zero exit code. | ||
11 | +process.on('unhandledRejection', err => { | ||
12 | + throw err; | ||
13 | +}); | ||
14 | + | ||
15 | +// Ensure environment variables are read. | ||
16 | +require('../config/env'); | ||
17 | + | ||
18 | +const jest = require('jest'); | ||
19 | +let argv = process.argv.slice(2); | ||
20 | + | ||
21 | +// Watch unless on CI, in coverage mode, or explicitly running all tests | ||
22 | +if ( | ||
23 | + !process.env.CI && | ||
24 | + argv.indexOf('--coverage') === -1 && | ||
25 | + argv.indexOf('--watchAll') === -1 | ||
26 | +) { | ||
27 | + argv.push('--watch'); | ||
28 | +} | ||
29 | + | ||
30 | + | ||
31 | +jest.run(argv); |
front/src/components/App/Component.tsx
0 → 100644
1 | +import * as React from "react"; | ||
2 | +import { Switch, Route, Link } from "react-router-dom"; | ||
3 | +import Main from "components/gadgets/Main"; | ||
4 | +import Nav from "components/gadgets/Nav"; | ||
5 | + | ||
6 | +class App extends React.Component { | ||
7 | + public render() { | ||
8 | + return ( | ||
9 | + <div> | ||
10 | + <Nav /> | ||
11 | + <Main /> | ||
12 | + </div> | ||
13 | + ); | ||
14 | + } | ||
15 | +} | ||
16 | + | ||
17 | +export default App; |
front/src/components/App/index.tsx
0 → 100644
front/src/components/App/styles.scss
0 → 100644
File mode changed
front/src/components/gadgets/Main/index.tsx
0 → 100644
1 | +import * as React from "react"; | ||
2 | +import { Switch, Route } from "react-router-dom"; | ||
3 | +import RequestPage from "components/pages/RequestPage"; | ||
4 | +import DronePage from "components/pages/DronePage"; | ||
5 | +import HomePage from "components/pages/HomePage"; | ||
6 | +import RegisterPage from "components/pages/RegisterPage"; | ||
7 | +import MyPage from "components/pages/MyPage"; | ||
8 | +import ContractPage from "components/pages/ContractPage"; | ||
9 | +import LoginPage from "components/pages/LoginPage"; | ||
10 | +import OrgPage from "components/pages/OrgPage"; | ||
11 | +import BuyPage from "components/pages/BuyPage"; | ||
12 | +import DatasetPage from "components/pages/DatasetPage"; | ||
13 | +import SearchPage from "components/pages/SearchPage"; | ||
14 | +import JudgePage from "components/pages/JudgePage"; | ||
15 | +import { withRouter, RouteComponentProps } from "react-router-dom"; | ||
16 | + | ||
17 | + | ||
18 | +require("./styles.scss"); | ||
19 | + | ||
20 | +interface MyProps extends RouteComponentProps<{}> {} | ||
21 | + | ||
22 | +class Main extends React.Component<MyProps> { | ||
23 | + componentDidUpdate(prevProps) { | ||
24 | + if (this.props.location !== prevProps.location) window.scrollTo(0, 0); | ||
25 | + } | ||
26 | + | ||
27 | + public render() { | ||
28 | + return ( | ||
29 | + <main className="Main"> | ||
30 | + <Switch> | ||
31 | + <Route exact path="/" component={HomePage} /> | ||
32 | + <Route exact path="/request" component={RequestPage} /> | ||
33 | + <Route exact path="/drone" component={DronePage} /> | ||
34 | + <Route exact path="/register" component={RegisterPage} /> | ||
35 | + <Route exact path="/my-page" component={MyPage} /> | ||
36 | + <Route exact path="/contract/:id" component={ContractPage} /> | ||
37 | + <Route exact path="/org" component={OrgPage} /> | ||
38 | + <Route exact path="/buy" component={BuyPage} /> | ||
39 | + <Route exact path="/judge/:id" component={JudgePage} /> | ||
40 | + <Route exact path="/search/:id" component={SearchPage} /> | ||
41 | + <Route exact path="/dataset" component={DatasetPage} /> | ||
42 | + <Route exact path="/login" component={LoginPage} /> | ||
43 | + </Switch> | ||
44 | + </main> | ||
45 | + ); | ||
46 | + } | ||
47 | +} | ||
48 | + | ||
49 | +export default withRouter(Main); |
front/src/components/gadgets/Nav/index.tsx
0 → 100644
1 | +import * as React from "react"; | ||
2 | +import { NavLink, Link } from "react-router-dom"; | ||
3 | +require("./styles.scss"); | ||
4 | + | ||
5 | +const activeStyle = { | ||
6 | + textDecoration: "underline" | ||
7 | +}; | ||
8 | + | ||
9 | +const Nav: React.SFC<{}> = () => ( | ||
10 | + <div className="nav"> | ||
11 | + <ul> | ||
12 | + <li> | ||
13 | + <NavLink exact to="/" activeStyle={activeStyle}> | ||
14 | + <p>홈</p> | ||
15 | + </NavLink> | ||
16 | + </li> | ||
17 | + <li> | ||
18 | + <NavLink exact to="/my-page" activeStyle={activeStyle}> | ||
19 | + <p>내페이지</p> | ||
20 | + </NavLink> | ||
21 | + </li> | ||
22 | + </ul> | ||
23 | + <ul> | ||
24 | + <li> | ||
25 | + <NavLink exact to="/drone" activeStyle={activeStyle}> | ||
26 | + <p>드론등록</p> | ||
27 | + </NavLink> | ||
28 | + </li> | ||
29 | + <li> | ||
30 | + <NavLink exact to="/request" activeStyle={activeStyle}> | ||
31 | + <p>허가신청</p> | ||
32 | + </NavLink> | ||
33 | + </li> | ||
34 | + </ul> | ||
35 | + <ul> | ||
36 | + <li> | ||
37 | + <NavLink exact to="/dataset" activeStyle={activeStyle}> | ||
38 | + <p>데이터등록</p> | ||
39 | + </NavLink> | ||
40 | + </li> | ||
41 | + <li> | ||
42 | + <NavLink exact to="/buy" activeStyle={activeStyle}> | ||
43 | + <p>데이터구매</p> | ||
44 | + </NavLink> | ||
45 | + </li> | ||
46 | + </ul> | ||
47 | + <ul> | ||
48 | + <li> | ||
49 | + <NavLink exact to="/login" activeStyle={activeStyle}> | ||
50 | + <p>로그인</p> | ||
51 | + </NavLink> | ||
52 | + </li> | ||
53 | + <li> | ||
54 | + <NavLink exact to="/register" activeStyle={activeStyle}> | ||
55 | + <p>가입</p> | ||
56 | + </NavLink> | ||
57 | + </li> | ||
58 | + <li> | ||
59 | + <button onClick={() => localStorage.clear()}>로그아웃</button> | ||
60 | + </li> | ||
61 | + </ul> | ||
62 | + </div> | ||
63 | +); | ||
64 | + | ||
65 | +export default Nav; |
front/src/components/gadgets/Nav/styles.scss
0 → 100644
1 | +import * as React from "react"; | ||
2 | +import { Link } from "react-router-dom"; | ||
3 | +import TextField from "material-ui/TextField"; | ||
4 | +import RaisedButton from "material-ui/RaisedButton"; | ||
5 | +import DatePicker from "material-ui/DatePicker"; | ||
6 | +import Toggle from "material-ui/Toggle"; | ||
7 | +import MenuItem from "material-ui/MenuItem"; | ||
8 | +import DropDownMenu from "material-ui/DropDownMenu"; | ||
9 | + | ||
10 | +require("./styles.scss"); | ||
11 | + | ||
12 | +const style = { | ||
13 | + width: "100%" | ||
14 | +}; | ||
15 | + | ||
16 | +interface Props { | ||
17 | + datasets: Array<any>; | ||
18 | + buyDataset: Function; | ||
19 | +} | ||
20 | + | ||
21 | +class BuyPage extends React.Component<Props> { | ||
22 | + public render() { | ||
23 | + const { props } = this; | ||
24 | + const { datasets, buyDataset } = props; | ||
25 | + return ( | ||
26 | + <div className="buy-page"> | ||
27 | + <h2>데이터셋 구매</h2> | ||
28 | + <ul> | ||
29 | + {datasets.map(dataset => ( | ||
30 | + <li key={dataset.id}> | ||
31 | + <h3>데이터</h3> | ||
32 | + <p>{dataset.comment}</p> | ||
33 | + <h3>촬영 기종</h3> | ||
34 | + <p>{dataset.producer.model.name}</p> | ||
35 | + <h3>소유자</h3> | ||
36 | + <p>{dataset.producer.owner.email}</p> | ||
37 | + <hr /> | ||
38 | + <RaisedButton | ||
39 | + label="구매" | ||
40 | + onClick={() => buyDataset(dataset.id)} | ||
41 | + /> | ||
42 | + </li> | ||
43 | + ))} | ||
44 | + </ul> | ||
45 | + </div> | ||
46 | + ); | ||
47 | + } | ||
48 | +} | ||
49 | + | ||
50 | +export default BuyPage; |
front/src/components/pages/BuyPage/index.tsx
0 → 100644
1 | +import Component from "./Component"; | ||
2 | +import * as React from "react"; | ||
3 | +import { Query, Mutation } from "react-apollo"; | ||
4 | +import { withRouter, RouteComponentProps } from "react-router"; | ||
5 | + | ||
6 | +const FETCH_ALL_DATASETS = require("./fetchAllDatasets.gql"); | ||
7 | +const BUY_DATASET = require("./buyDataset.gql"); | ||
8 | + | ||
9 | +class FetchAllDatasetsQuery extends Query<{ | ||
10 | + datasets: any; | ||
11 | +}> {} | ||
12 | + | ||
13 | +class CreateContractMutation extends Mutation<{}> {} | ||
14 | + | ||
15 | +const update = (history: any) => (cache, result) => history.push("/buy"); | ||
16 | + | ||
17 | +const QueryComponent: React.SFC<RouteComponentProps<{}>> = ({ history }) => ( | ||
18 | + <FetchAllDatasetsQuery query={FETCH_ALL_DATASETS} fetchPolicy="network-only"> | ||
19 | + {({ loading, error, data }) => { | ||
20 | + if (loading) return <p>Loading...</p>; | ||
21 | + else if (error) return <p>Error :(</p>; | ||
22 | + else | ||
23 | + return ( | ||
24 | + <CreateContractMutation | ||
25 | + mutation={BUY_DATASET} | ||
26 | + update={update(history)} | ||
27 | + > | ||
28 | + {mutate => { | ||
29 | + const buyDataset = id => mutate({ variables: { id } }); | ||
30 | + return ( | ||
31 | + <Component datasets={data!.datasets} buyDataset={buyDataset} /> | ||
32 | + ); | ||
33 | + }} | ||
34 | + </CreateContractMutation> | ||
35 | + ); | ||
36 | + }} | ||
37 | + </FetchAllDatasetsQuery> | ||
38 | +); | ||
39 | + | ||
40 | +export default QueryComponent; |
1 | +import * as React from "react"; | ||
2 | +import { Link } from "react-router-dom"; | ||
3 | +import styled from "styled-components"; | ||
4 | +import TextField from "material-ui/TextField"; | ||
5 | +import RaisedButton from "material-ui/RaisedButton"; | ||
6 | + | ||
7 | +require("./styles.scss"); | ||
8 | + | ||
9 | +const style = { | ||
10 | + width: "100%" | ||
11 | +}; | ||
12 | + | ||
13 | +interface Props { | ||
14 | + contract: any; | ||
15 | +} | ||
16 | + | ||
17 | +const statusLog = (status, review) => { | ||
18 | + if (status === "wait") return "심사중"; | ||
19 | + else if (status === "ok") return "승인"; | ||
20 | + else return `반려사유: ${review}`; | ||
21 | +}; | ||
22 | + | ||
23 | +class ContractPage extends React.Component<Props> { | ||
24 | + public render() { | ||
25 | + const { props } = this; | ||
26 | + const { contract } = props; | ||
27 | + return ( | ||
28 | + <div className="contract-page"> | ||
29 | + <h2>허가서</h2> | ||
30 | + <h3>날짜</h3> | ||
31 | + <p>{contract.date}</p> | ||
32 | + <h3>지역</h3> | ||
33 | + <p>{contract.area}</p> | ||
34 | + <h3>상세주소</h3> | ||
35 | + <p>{contract.address}</p> | ||
36 | + <h3>사유</h3> | ||
37 | + <p>{contract.reason}</p> | ||
38 | + <h3>신청 상태</h3> | ||
39 | + <p>{statusLog(contract.status, contract.review)}</p> | ||
40 | + </div> | ||
41 | + ); | ||
42 | + } | ||
43 | +} | ||
44 | + | ||
45 | +export default ContractPage; |
1 | +import Component from "./Component"; | ||
2 | +import * as React from "react"; | ||
3 | +import { graphql, Query, Mutation } from "react-apollo"; | ||
4 | +import { withRouter, RouteComponentProps } from "react-router"; | ||
5 | + | ||
6 | +const FETCH_CONTRACT = require("./fetchContract.gql"); | ||
7 | + | ||
8 | +interface Props { | ||
9 | + contract: any; | ||
10 | +} | ||
11 | + | ||
12 | +class FetchContractQuery extends Query<Props> {} | ||
13 | + | ||
14 | +const QueryComponent: React.SFC<RouteComponentProps<{}>> = props => ( | ||
15 | + <FetchContractQuery | ||
16 | + query={FETCH_CONTRACT} | ||
17 | + fetchPolicy="network-only" | ||
18 | + variables={{ id: props.match.params["id"] }} | ||
19 | + > | ||
20 | + {({ loading, error, data }) => { | ||
21 | + if (loading) return <p>Loading...</p>; | ||
22 | + else if (error || !data!.contract) return <p>Error :(</p>; | ||
23 | + else return <Component contract={data!.contract} />; | ||
24 | + }} | ||
25 | + </FetchContractQuery> | ||
26 | +); | ||
27 | + | ||
28 | +export default withRouter(QueryComponent); |
1 | +import * as React from "react"; | ||
2 | +import { Link } from "react-router-dom"; | ||
3 | +import TextField from "material-ui/TextField"; | ||
4 | +import RaisedButton from "material-ui/RaisedButton"; | ||
5 | +import DatePicker from "material-ui/DatePicker"; | ||
6 | +import Toggle from "material-ui/Toggle"; | ||
7 | +import MenuItem from "material-ui/MenuItem"; | ||
8 | +import DropDownMenu from "material-ui/DropDownMenu"; | ||
9 | + | ||
10 | +require("./styles.scss"); | ||
11 | + | ||
12 | +const style = { | ||
13 | + width: "100%" | ||
14 | +}; | ||
15 | + | ||
16 | +interface Props { | ||
17 | + user: any; | ||
18 | + createDataset(input: any): void; | ||
19 | +} | ||
20 | + | ||
21 | +interface State { | ||
22 | + comment: string; | ||
23 | + droneId: string; | ||
24 | +} | ||
25 | + | ||
26 | +class RequestPage extends React.Component<Props, State> { | ||
27 | + state = { | ||
28 | + comment: "", | ||
29 | + droneId: "" | ||
30 | + }; | ||
31 | + | ||
32 | + handleChangeComment = event => this.setState({ comment: event.target.value }); | ||
33 | + handleChangeDrone = (event, index, droneId) => this.setState({ droneId }); | ||
34 | + | ||
35 | + public render() { | ||
36 | + const { props, state } = this; | ||
37 | + const minDate = new Date(); | ||
38 | + const maxDate = new Date(); | ||
39 | + maxDate.setDate(maxDate.getDate() + 90); | ||
40 | + const { createDataset } = props; | ||
41 | + | ||
42 | + return ( | ||
43 | + <div className="dataset-page"> | ||
44 | + <h2>데이터셋 등록</h2> | ||
45 | + <TextField | ||
46 | + hintText="설명" | ||
47 | + type="text" | ||
48 | + value={state.comment} | ||
49 | + onChange={this.handleChangeComment} | ||
50 | + style={style} | ||
51 | + /> | ||
52 | + <h3>파일</h3> | ||
53 | + <input type="file" /> | ||
54 | + <h3>촬영한 드론</h3> | ||
55 | + <DropDownMenu | ||
56 | + maxHeight={300} | ||
57 | + value={state.droneId} | ||
58 | + onChange={this.handleChangeDrone} | ||
59 | + > | ||
60 | + {props.user.drones.map(drone => ( | ||
61 | + <MenuItem | ||
62 | + key={drone.id} | ||
63 | + value={drone.id} | ||
64 | + primaryText={`${drone.name} (${drone.model.name})`} | ||
65 | + /> | ||
66 | + ))} | ||
67 | + </DropDownMenu> | ||
68 | + <RaisedButton | ||
69 | + label="확인" | ||
70 | + primary={true} | ||
71 | + onClick={() => createDataset(state)} | ||
72 | + /> | ||
73 | + </div> | ||
74 | + ); | ||
75 | + } | ||
76 | +} | ||
77 | + | ||
78 | +export default RequestPage; |
1 | +import Component from "./Component"; | ||
2 | +import * as React from "react"; | ||
3 | +import { Query, Mutation } from "react-apollo"; | ||
4 | +import { withRouter, RouteComponentProps } from "react-router"; | ||
5 | + | ||
6 | +const FETCH_CURRENT_USER = require("./fetchCurrentUser.gql"); | ||
7 | +const CREATE_DATASET = require("./createDataset.gql"); | ||
8 | + | ||
9 | +class FetchCurrentUserQuery extends Query<{ | ||
10 | + user: any; | ||
11 | + orgs: any; | ||
12 | +}> {} | ||
13 | + | ||
14 | +class CreateContractMutation extends Mutation<{}> {} | ||
15 | + | ||
16 | +const update = (history: any) => (cache, result) => history.push("/my-page"); | ||
17 | + | ||
18 | +const QueryComponent: React.SFC<RouteComponentProps<{}>> = ({ history }) => ( | ||
19 | + <FetchCurrentUserQuery query={FETCH_CURRENT_USER} fetchPolicy="network-only"> | ||
20 | + {({ loading, error, data }) => { | ||
21 | + if (loading) return <p>Loading...</p>; | ||
22 | + else if (error) return <p>Error :(</p>; | ||
23 | + else | ||
24 | + return ( | ||
25 | + <CreateContractMutation | ||
26 | + mutation={CREATE_DATASET} | ||
27 | + update={update(history)} | ||
28 | + > | ||
29 | + {mutate => { | ||
30 | + const createDataset = input => mutate({ variables: { input } }); | ||
31 | + return ( | ||
32 | + <Component user={data!.user} createDataset={createDataset} /> | ||
33 | + ); | ||
34 | + }} | ||
35 | + </CreateContractMutation> | ||
36 | + ); | ||
37 | + }} | ||
38 | + </FetchCurrentUserQuery> | ||
39 | +); | ||
40 | + | ||
41 | +export default withRouter(QueryComponent); |
1 | +import * as R from "ramda"; | ||
2 | +import * as React from "react"; | ||
3 | +import { Link } from "react-router-dom"; | ||
4 | +import DropDownMenu from "material-ui/DropDownMenu"; | ||
5 | +import MenuItem from "material-ui/MenuItem"; | ||
6 | +import TextField from "material-ui/TextField"; | ||
7 | +import RaisedButton from "material-ui/RaisedButton"; | ||
8 | + | ||
9 | +require("./styles.scss"); | ||
10 | + | ||
11 | +interface State { | ||
12 | + modelId: string; | ||
13 | + name: string; | ||
14 | +} | ||
15 | + | ||
16 | +interface Props { | ||
17 | + models: Array<any>; | ||
18 | + registerDrone(input: any): void; | ||
19 | +} | ||
20 | + | ||
21 | +const style = { | ||
22 | + width: "100%" | ||
23 | +}; | ||
24 | + | ||
25 | +class DronePage extends React.Component<Props, State> { | ||
26 | + state = { | ||
27 | + modelId: "", | ||
28 | + name: "" | ||
29 | + }; | ||
30 | + | ||
31 | + handleChangeModel = (event, index, modelId) => this.setState({ modelId }); | ||
32 | + handleChangeName = event => this.setState({ name: event.target.value }); | ||
33 | + | ||
34 | + public render() { | ||
35 | + const { props, state } = this; | ||
36 | + return ( | ||
37 | + <div className="drone-page"> | ||
38 | + <h2>드론등록</h2> | ||
39 | + <TextField | ||
40 | + hintText="이름" | ||
41 | + type="text" | ||
42 | + value={state.name} | ||
43 | + onChange={this.handleChangeName} | ||
44 | + style={style} | ||
45 | + /> | ||
46 | + <h3>모델</h3> | ||
47 | + <DropDownMenu | ||
48 | + maxHeight={300} | ||
49 | + value={state.modelId} | ||
50 | + onChange={this.handleChangeModel} | ||
51 | + > | ||
52 | + {props.models.map(model => ( | ||
53 | + <MenuItem | ||
54 | + key={model.id} | ||
55 | + value={model.id} | ||
56 | + primaryText={`${model.name}`} | ||
57 | + /> | ||
58 | + ))} | ||
59 | + </DropDownMenu> | ||
60 | + <RaisedButton | ||
61 | + label="등록하기" | ||
62 | + primary={true} | ||
63 | + style={style} | ||
64 | + onClick={() => props.registerDrone(state)} | ||
65 | + /> | ||
66 | + </div> | ||
67 | + ); | ||
68 | + } | ||
69 | +} | ||
70 | + | ||
71 | +export default DronePage; |
1 | +import Component from "./Component"; | ||
2 | +import * as React from "react"; | ||
3 | +import { Query, Mutation } from "react-apollo"; | ||
4 | +import { withRouter, RouteComponentProps } from "react-router"; | ||
5 | + | ||
6 | +const FETCH_ALL_MODELS = require("./fetchAllModels.gql"); | ||
7 | +const REGISTER_DRONE = require("./registerDrone.gql"); | ||
8 | + | ||
9 | +class FetchAllModelsQuery extends Query<{ | ||
10 | + models: Array<any>; | ||
11 | +}> {} | ||
12 | + | ||
13 | +class RegisterDroneMutation extends Mutation<{}> {} | ||
14 | + | ||
15 | +const update = (history: any) => (cache, result) => history.push("/my-page"); | ||
16 | + | ||
17 | +const QueryComponent: React.SFC<RouteComponentProps<{}>> = ({ history }) => ( | ||
18 | + <FetchAllModelsQuery query={FETCH_ALL_MODELS}> | ||
19 | + {({ loading, error, data }) => { | ||
20 | + if (loading) return <p>Loading...</p>; | ||
21 | + else if (error) return <p>Error :(</p>; | ||
22 | + else | ||
23 | + return ( | ||
24 | + <RegisterDroneMutation | ||
25 | + mutation={REGISTER_DRONE} | ||
26 | + update={update(history)} | ||
27 | + > | ||
28 | + {mutate => { | ||
29 | + const registerDrone = input => mutate({ variables: { input } }); | ||
30 | + return ( | ||
31 | + <Component | ||
32 | + models={data!.models} | ||
33 | + registerDrone={registerDrone} | ||
34 | + /> | ||
35 | + ); | ||
36 | + }} | ||
37 | + </RegisterDroneMutation> | ||
38 | + ); | ||
39 | + }} | ||
40 | + </FetchAllModelsQuery> | ||
41 | +); | ||
42 | + | ||
43 | +export default withRouter(QueryComponent); |
1 | +import * as R from "ramda"; | ||
2 | +import * as React from "react"; | ||
3 | + | ||
4 | +require("./styles.scss"); | ||
5 | + | ||
6 | +class HomePage extends React.Component<{}> { | ||
7 | + public render() { | ||
8 | + const { props, state } = this; | ||
9 | + return ( | ||
10 | + <div className="home-page"> | ||
11 | + <h1>1조 과제 시연</h1> | ||
12 | + </div> | ||
13 | + ); | ||
14 | + } | ||
15 | +} | ||
16 | + | ||
17 | +export default HomePage; |
1 | +import * as React from "react"; | ||
2 | +import { Link } from "react-router-dom"; | ||
3 | +import styled from "styled-components"; | ||
4 | +import TextField from "material-ui/TextField"; | ||
5 | +import RaisedButton from "material-ui/RaisedButton"; | ||
6 | + | ||
7 | +require("./styles.scss"); | ||
8 | + | ||
9 | +const style = { | ||
10 | + // width: "100%" | ||
11 | +}; | ||
12 | + | ||
13 | +interface Props { | ||
14 | + org: any; | ||
15 | + confirm: Function; | ||
16 | + reject: Function; | ||
17 | +} | ||
18 | + | ||
19 | +class Tester extends React.Component<any> { | ||
20 | + state = { review: "" }; | ||
21 | + handleChangeReview = event => this.setState({ review: event.target.value }); | ||
22 | + public render() { | ||
23 | + const { props, state } = this; | ||
24 | + const { id, confirm, reject } = props; | ||
25 | + return ( | ||
26 | + <div> | ||
27 | + <RaisedButton | ||
28 | + label="승인" | ||
29 | + primary={true} | ||
30 | + style={style} | ||
31 | + onClick={() => confirm(id, state.review)} | ||
32 | + /> | ||
33 | + <hr /> | ||
34 | + <TextField | ||
35 | + hintText="사유" | ||
36 | + type="text" | ||
37 | + value={state.review} | ||
38 | + onChange={this.handleChangeReview} | ||
39 | + style={style} | ||
40 | + /> | ||
41 | + <RaisedButton | ||
42 | + label="반려" | ||
43 | + primary={true} | ||
44 | + style={style} | ||
45 | + onClick={() => reject(id, state.review)} | ||
46 | + /> | ||
47 | + </div> | ||
48 | + ); | ||
49 | + } | ||
50 | +} | ||
51 | + | ||
52 | +class JudgePage extends React.Component<Props> { | ||
53 | + public render() { | ||
54 | + const { props, state } = this; | ||
55 | + const { org } = props; | ||
56 | + return ( | ||
57 | + <div className="judge-page"> | ||
58 | + <h2>신청서</h2> | ||
59 | + <ul> | ||
60 | + {org.contracts | ||
61 | + .filter(contract => contract.status === "wait") | ||
62 | + .map(contract => ( | ||
63 | + <li key={contract.id}> | ||
64 | + <h3>사유</h3> | ||
65 | + <p>{contract.reason}</p> | ||
66 | + <h3>주소</h3> | ||
67 | + <p>{contract.address}</p> | ||
68 | + <h3>드론</h3> | ||
69 | + <p> | ||
70 | + {contract.drone.name}({contract.drone.model.name}) | ||
71 | + </p> | ||
72 | + <Tester | ||
73 | + id={contract.id} | ||
74 | + confirm={props.confirm} | ||
75 | + reject={props.reject} | ||
76 | + /> | ||
77 | + <br /> | ||
78 | + </li> | ||
79 | + ))} | ||
80 | + </ul> | ||
81 | + </div> | ||
82 | + ); | ||
83 | + } | ||
84 | +} | ||
85 | + | ||
86 | +export default JudgePage; |
1 | +import Component from "./Component"; | ||
2 | +import * as React from "react"; | ||
3 | +import { graphql, Query, Mutation } from "react-apollo"; | ||
4 | +import { withRouter, RouteComponentProps } from "react-router"; | ||
5 | + | ||
6 | +const FETCH_ORG = require("./fetchOrg.gql"); | ||
7 | +const JUDGE_CONTRACT = require("./judgeContract.gql"); | ||
8 | + | ||
9 | +interface Props { | ||
10 | + org: any; | ||
11 | +} | ||
12 | + | ||
13 | +class FetchContractQuery extends Query<Props> {} | ||
14 | +class JudgeContractMutation extends Mutation<{}> {} | ||
15 | + | ||
16 | +const update = (history: any) => (cache, result) => history.push('/org'); | ||
17 | + | ||
18 | +const QueryComponent: React.SFC<RouteComponentProps<{}> & Props> = props => ( | ||
19 | + <FetchContractQuery | ||
20 | + query={FETCH_ORG} | ||
21 | + fetchPolicy="network-only" | ||
22 | + variables={{ id: props.match.params["id"] }} | ||
23 | + > | ||
24 | + {({ loading, error, data }) => { | ||
25 | + if (loading) return <p>Loading...</p>; | ||
26 | + else if (error || !data!.org) return <p>Error :(</p>; | ||
27 | + else | ||
28 | + return ( | ||
29 | + <JudgeContractMutation | ||
30 | + mutation={JUDGE_CONTRACT} | ||
31 | + update={update(history)} | ||
32 | + > | ||
33 | + {mutate => { | ||
34 | + const confirm = (id, review) => { | ||
35 | + mutate({ variables: { id, ok: true, review } }); | ||
36 | + props.history.push('/org'); | ||
37 | + } | ||
38 | + const reject = (id, review) => { | ||
39 | + mutate({ variables: { id, ok: false, review } }); | ||
40 | + props.history.push('/org'); | ||
41 | + } | ||
42 | + return ( | ||
43 | + <Component org={data!.org} confirm={confirm} reject={reject} /> | ||
44 | + ); | ||
45 | + }} | ||
46 | + </JudgeContractMutation> | ||
47 | + ); | ||
48 | + }} | ||
49 | + </FetchContractQuery> | ||
50 | +); | ||
51 | + | ||
52 | +export default withRouter(QueryComponent); |
1 | +import * as R from "ramda"; | ||
2 | +import * as React from "react"; | ||
3 | +import TextField from "material-ui/TextField"; | ||
4 | +import RaisedButton from "material-ui/RaisedButton"; | ||
5 | +import { History } from "history"; | ||
6 | + | ||
7 | +require("./styles.scss"); | ||
8 | + | ||
9 | +interface Props { | ||
10 | + login: (input) => void; | ||
11 | + history: History; | ||
12 | +} | ||
13 | + | ||
14 | +const style = { | ||
15 | + width: "100%" | ||
16 | +}; | ||
17 | + | ||
18 | +class LoginPage extends React.Component<Props> { | ||
19 | + state = { | ||
20 | + // email: "", | ||
21 | + // password: "" | ||
22 | + email: "db@khu.ac.kr", | ||
23 | + password: "db" | ||
24 | + }; | ||
25 | + | ||
26 | + handleChangeEmail = event => this.setState({ email: event.target.value }); | ||
27 | + handleChangePassword = event => | ||
28 | + this.setState({ password: event.target.value }); | ||
29 | + | ||
30 | + public render() { | ||
31 | + const { props, state } = this; | ||
32 | + return ( | ||
33 | + <div className="login-page"> | ||
34 | + <form> | ||
35 | + <h2>로그인</h2> | ||
36 | + <TextField | ||
37 | + hintText="email" | ||
38 | + type="email" | ||
39 | + value={state.email} | ||
40 | + onChange={this.handleChangeEmail} | ||
41 | + style={style} | ||
42 | + /> | ||
43 | + <TextField | ||
44 | + hintText="password" | ||
45 | + type="password" | ||
46 | + value={state.password} | ||
47 | + onChange={this.handleChangePassword} | ||
48 | + style={style} | ||
49 | + /> | ||
50 | + <RaisedButton | ||
51 | + label="로그인" | ||
52 | + style={style} | ||
53 | + onClick={() => props.login(state)} | ||
54 | + /> | ||
55 | + {/* | ||
56 | + <RaisedButton | ||
57 | + label="회원가입" | ||
58 | + primary={true} | ||
59 | + style={style} | ||
60 | + onClick={() => props.history.push("register")} | ||
61 | + /> */} | ||
62 | + </form> | ||
63 | + </div> | ||
64 | + ); | ||
65 | + } | ||
66 | +} | ||
67 | + | ||
68 | +export default LoginPage; |
1 | +import * as React from "react"; | ||
2 | +import Component from "./Component"; | ||
3 | +import { Mutation } from "react-apollo"; | ||
4 | +import { withRouter, RouteComponentProps } from "react-router"; | ||
5 | +import * as R from "ramda"; | ||
6 | +import { History } from "history"; | ||
7 | + | ||
8 | +const LOGIN = require("./login.gql"); | ||
9 | + | ||
10 | +class LoginMutation extends Mutation<{}> {} | ||
11 | + | ||
12 | +interface Variables { | ||
13 | + input: { | ||
14 | + email: string; | ||
15 | + password: string; | ||
16 | + }; | ||
17 | +} | ||
18 | + | ||
19 | +const update = (history: History) => (cache, { data }) => { | ||
20 | + const { login } = data; | ||
21 | + if (R.isNil(login)) { | ||
22 | + alert("로그인 실패"); | ||
23 | + } else { | ||
24 | + localStorage.setItem("token", login.token); | ||
25 | + history.push("/"); | ||
26 | + } | ||
27 | +}; | ||
28 | + | ||
29 | +const MutationComponent: React.SFC<RouteComponentProps<Variables>> = ({ | ||
30 | + match, | ||
31 | + history | ||
32 | +}) => ( | ||
33 | + <LoginMutation mutation={LOGIN} update={update(history)}> | ||
34 | + {mutate => { | ||
35 | + const login = input => mutate({ variables: { input } }); | ||
36 | + return <Component login={login} history={history} />; | ||
37 | + }} | ||
38 | + </LoginMutation> | ||
39 | +); | ||
40 | + | ||
41 | +export default withRouter(MutationComponent); |
1 | +import * as React from "react"; | ||
2 | +import { Link } from "react-router-dom"; | ||
3 | +import { List, ListItem } from "material-ui/List"; | ||
4 | +import Avatar from "material-ui/Avatar"; | ||
5 | + | ||
6 | +require("./styles.scss"); | ||
7 | + | ||
8 | +interface Props { | ||
9 | + user: any; | ||
10 | + history: any; | ||
11 | +} | ||
12 | + | ||
13 | +class UserPage extends React.Component<Props> { | ||
14 | + public render() { | ||
15 | + const { user, history } = this.props; | ||
16 | + console.log(this.props); | ||
17 | + return ( | ||
18 | + <div className="my-page"> | ||
19 | + <h2>내 페이지</h2> | ||
20 | + <h3>드론 목록</h3> | ||
21 | + <List> | ||
22 | + {user.drones.map(drone => ( | ||
23 | + <ListItem | ||
24 | + key={drone.id} | ||
25 | + primaryText={`${drone.name} (${drone.model.name})`} | ||
26 | + leftAvatar={ | ||
27 | + <Avatar src={`/assets/img/${drone.model.name}.jpg`} /> | ||
28 | + } | ||
29 | + /> | ||
30 | + ))} | ||
31 | + </List> | ||
32 | + <h3>허가서 목록</h3> | ||
33 | + <List> | ||
34 | + {user.contracts.map(contract => ( | ||
35 | + <ListItem | ||
36 | + key={contract.id} | ||
37 | + primaryText={`${contract.reason} (${contract.drone.name})`} | ||
38 | + leftAvatar={ | ||
39 | + <Avatar src={`/assets/img/${contract.drone.model.name}.jpg`} /> | ||
40 | + } | ||
41 | + onClick={() => history.push(`/contract/${contract.id}`)} | ||
42 | + /> | ||
43 | + ))} | ||
44 | + </List> | ||
45 | + <h3>구매한 데이터셋</h3> | ||
46 | + <ul> | ||
47 | + {user.datasets.map(dataset => ( | ||
48 | + <li key={dataset.id}> | ||
49 | + <h4>데이터</h4> | ||
50 | + <p>{dataset.comment}</p> | ||
51 | + <h4>촬영한 드론</h4> | ||
52 | + <p>{dataset.producer.name}({dataset.producer.model.name})</p> | ||
53 | + </li> | ||
54 | + ))} | ||
55 | + </ul> | ||
56 | + </div> | ||
57 | + ); | ||
58 | + } | ||
59 | +} | ||
60 | + | ||
61 | +export default UserPage; |
1 | +query FetchCurrentUser { | ||
2 | + user: fetchCurrentUser { | ||
3 | + id | ||
4 | + drones { | ||
5 | + id | ||
6 | + name | ||
7 | + date | ||
8 | + model { | ||
9 | + id | ||
10 | + name | ||
11 | + } | ||
12 | + } | ||
13 | + contracts { | ||
14 | + id | ||
15 | + reason | ||
16 | + date | ||
17 | + drone { | ||
18 | + id | ||
19 | + date | ||
20 | + name | ||
21 | + model { | ||
22 | + id | ||
23 | + name | ||
24 | + } | ||
25 | + } | ||
26 | + } | ||
27 | + datasets { | ||
28 | + id | ||
29 | + comment | ||
30 | + producer { | ||
31 | + id | ||
32 | + name | ||
33 | + model { | ||
34 | + id | ||
35 | + name | ||
36 | + } | ||
37 | + } | ||
38 | + } | ||
39 | + } | ||
40 | +} |
front/src/components/pages/MyPage/index.tsx
0 → 100644
1 | +import * as React from "react"; | ||
2 | +import Component from "./Component"; | ||
3 | +import { graphql, Query, Mutation } from "react-apollo"; | ||
4 | +import { withRouter, RouteComponentProps } from "react-router"; | ||
5 | +const FETCH_CURRENT_USER = require("./fetchCurrentUser.gql"); | ||
6 | + | ||
7 | +interface Props { | ||
8 | + user: any; | ||
9 | +} | ||
10 | + | ||
11 | +class FetchUserPageQuery extends Query<Props> {} | ||
12 | + | ||
13 | +const QueryComponent: React.SFC<RouteComponentProps<{}>> = props => ( | ||
14 | + <FetchUserPageQuery | ||
15 | + query={FETCH_CURRENT_USER} fetchPolicy="network-only"> | ||
16 | + {({ loading, error, data }) => { | ||
17 | + if (loading) return <p>Loading...</p>; | ||
18 | + else if (error || !data!.user) return <p>Error :(</p>; | ||
19 | + else return <Component user={data!.user} history={props.history} />; | ||
20 | + }} | ||
21 | + </FetchUserPageQuery> | ||
22 | +); | ||
23 | + | ||
24 | +export default withRouter(QueryComponent); |
1 | +import * as React from "react"; | ||
2 | +import { Link } from "react-router-dom"; | ||
3 | +import styled from "styled-components"; | ||
4 | +import TextField from "material-ui/TextField"; | ||
5 | +import RaisedButton from "material-ui/RaisedButton"; | ||
6 | + | ||
7 | +require("./styles.scss"); | ||
8 | + | ||
9 | +const style = { | ||
10 | + width: "100%" | ||
11 | +}; | ||
12 | + | ||
13 | +interface Props { | ||
14 | + orgs: Array<any>; | ||
15 | +} | ||
16 | + | ||
17 | +class OrgPage extends React.Component<Props> { | ||
18 | + public render() { | ||
19 | + const { props } = this; | ||
20 | + const { orgs } = props; | ||
21 | + console.log(orgs, orgs.length); | ||
22 | + return ( | ||
23 | + <div className="org-page"> | ||
24 | + <h2>신청서 심사</h2> | ||
25 | + <ul> | ||
26 | + {orgs.map(org => ( | ||
27 | + <li key={org.id}> | ||
28 | + <h3>{org.name}</h3> | ||
29 | + <p><Link to={`/judge/${org.id}`}>심사</Link></p> | ||
30 | + <p><Link to={`/search/${org.id}`}>질의</Link></p> | ||
31 | + </li> | ||
32 | + ))} | ||
33 | + </ul> | ||
34 | + </div> | ||
35 | + ); | ||
36 | + } | ||
37 | +} | ||
38 | + | ||
39 | +export default OrgPage; |
front/src/components/pages/OrgPage/index.tsx
0 → 100644
1 | +import Component from "./Component"; | ||
2 | +import * as React from "react"; | ||
3 | +import { graphql, Query, Mutation } from "react-apollo"; | ||
4 | +import { withRouter, RouteComponentProps } from "react-router"; | ||
5 | + | ||
6 | +const FETCH_ALL_ORGS = require("./fetchAllOrgs.gql"); | ||
7 | + | ||
8 | +interface Props { | ||
9 | + orgs: Array<any>; | ||
10 | +} | ||
11 | + | ||
12 | +class FetchContractQuery extends Query<Props> {} | ||
13 | + | ||
14 | +const QueryComponent: React.SFC<RouteComponentProps<{}>> = props => ( | ||
15 | + <FetchContractQuery | ||
16 | + query={FETCH_ALL_ORGS} | ||
17 | + fetchPolicy="network-only" | ||
18 | + > | ||
19 | + {({ loading, error, data }) => { | ||
20 | + if (loading) return <p>Loading...</p>; | ||
21 | + else if (error || !data!.orgs) return <p>Error :(</p>; | ||
22 | + else return <Component orgs={data!.orgs} />; | ||
23 | + }} | ||
24 | + </FetchContractQuery> | ||
25 | +); | ||
26 | + | ||
27 | +export default withRouter(QueryComponent); |
1 | +import * as React from "react"; | ||
2 | +import { Link } from "react-router-dom"; | ||
3 | +import styled from "styled-components"; | ||
4 | +import TextField from "material-ui/TextField"; | ||
5 | +import RaisedButton from "material-ui/RaisedButton"; | ||
6 | + | ||
7 | +require("./styles.scss"); | ||
8 | + | ||
9 | +interface UserInput { | ||
10 | + email: string; | ||
11 | + password: string; | ||
12 | +} | ||
13 | + | ||
14 | +const style = { | ||
15 | + width: "100%" | ||
16 | +}; | ||
17 | + | ||
18 | +interface Props { | ||
19 | + createUser: (input: UserInput) => void; | ||
20 | +} | ||
21 | + | ||
22 | +class RegisterPage extends React.Component<Props> { | ||
23 | + state = { | ||
24 | + password: "", | ||
25 | + email: "" | ||
26 | + }; | ||
27 | + | ||
28 | + handleChangeEmail = event => this.setState({ email: event.target.value }); | ||
29 | + handleChangePassword = event => | ||
30 | + this.setState({ password: event.target.value }); | ||
31 | + | ||
32 | + public render() { | ||
33 | + const createUser = this.props.createUser; | ||
34 | + const { props, state } = this; | ||
35 | + return ( | ||
36 | + <div className="register-page"> | ||
37 | + <h2>가입하기</h2> | ||
38 | + <TextField | ||
39 | + hintText="email" | ||
40 | + type="email" | ||
41 | + value={state.email} | ||
42 | + onChange={this.handleChangeEmail} | ||
43 | + style={style} | ||
44 | + /> | ||
45 | + <TextField | ||
46 | + hintText="password" | ||
47 | + type="password" | ||
48 | + value={state.password} | ||
49 | + onChange={this.handleChangePassword} | ||
50 | + style={style} | ||
51 | + /> | ||
52 | + <RaisedButton | ||
53 | + label="확인" | ||
54 | + primary={true} | ||
55 | + style={style} | ||
56 | + onClick={() => createUser(state)} | ||
57 | + /> | ||
58 | + </div> | ||
59 | + ); | ||
60 | + } | ||
61 | +} | ||
62 | + | ||
63 | +export default RegisterPage; |
1 | +import Component from "./Component"; | ||
2 | +import * as React from "react"; | ||
3 | +import { graphql, Query, Mutation } from "react-apollo"; | ||
4 | +import { withRouter, RouteComponentProps } from "react-router"; | ||
5 | + | ||
6 | +const CREATE_USER = require("./createUser.gql"); | ||
7 | + | ||
8 | +class CreateUserMutation extends Mutation<{}> {} | ||
9 | + | ||
10 | +interface Variables { | ||
11 | + id: string; | ||
12 | +} | ||
13 | + | ||
14 | +const update = (history: any) => (cache, result) => history.push('/'); | ||
15 | + | ||
16 | +const QueryComponent: React.SFC<RouteComponentProps<Variables>> = ({ | ||
17 | + match, | ||
18 | + history | ||
19 | +}) => ( | ||
20 | + <CreateUserMutation mutation={CREATE_USER} update={update(history)}> | ||
21 | + {mutate => { | ||
22 | + const createUser = input => mutate({ variables: { input } }); | ||
23 | + return <Component createUser={createUser} />; | ||
24 | + }} | ||
25 | + </CreateUserMutation> | ||
26 | +); | ||
27 | + | ||
28 | +export default withRouter(QueryComponent); |
1 | +import * as React from "react"; | ||
2 | +import { Link } from "react-router-dom"; | ||
3 | +import TextField from "material-ui/TextField"; | ||
4 | +import RaisedButton from "material-ui/RaisedButton"; | ||
5 | +import DatePicker from "material-ui/DatePicker"; | ||
6 | +import Toggle from "material-ui/Toggle"; | ||
7 | +import MenuItem from "material-ui/MenuItem"; | ||
8 | +import DropDownMenu from "material-ui/DropDownMenu"; | ||
9 | + | ||
10 | +require("./styles.scss"); | ||
11 | + | ||
12 | +const style = { | ||
13 | + width: "100%" | ||
14 | +}; | ||
15 | + | ||
16 | +interface Props { | ||
17 | + user: any; | ||
18 | + orgs: any; | ||
19 | + createContract(input: any): void; | ||
20 | +} | ||
21 | + | ||
22 | +interface State { | ||
23 | + reason: string; | ||
24 | + date: Date; | ||
25 | + droneId: string; | ||
26 | + area: string; | ||
27 | + address: string; | ||
28 | +} | ||
29 | + | ||
30 | +class RequestPage extends React.Component<Props, State> { | ||
31 | + state = { | ||
32 | + reason: "", | ||
33 | + date: new Date(), | ||
34 | + droneId: "", | ||
35 | + area: "지역", | ||
36 | + address: "" | ||
37 | + }; | ||
38 | + | ||
39 | + handleChangeReason = event => this.setState({ reason: event.target.value }); | ||
40 | + handleChangeDate = (event, date) => this.setState({ date }); | ||
41 | + handleChangeDrone = (event, index, droneId) => this.setState({ droneId }); | ||
42 | + handleChangeArea = (event, index, area) => this.setState({ area }); | ||
43 | + handleChangeAddress = event => this.setState({ address: event.target.value }); | ||
44 | + | ||
45 | + public render() { | ||
46 | + const { props, state } = this; | ||
47 | + const minDate = new Date(); | ||
48 | + const maxDate = new Date(); | ||
49 | + maxDate.setDate(maxDate.getDate() + 90); | ||
50 | + | ||
51 | + return ( | ||
52 | + <div className="request-page"> | ||
53 | + <h2>허가 신청서 작성</h2> | ||
54 | + <TextField | ||
55 | + hintText="사유" | ||
56 | + type="text" | ||
57 | + value={state.reason} | ||
58 | + onChange={this.handleChangeReason} | ||
59 | + style={style} | ||
60 | + /> | ||
61 | + <DropDownMenu | ||
62 | + maxHeight={300} | ||
63 | + value={state.droneId} | ||
64 | + onChange={this.handleChangeDrone} | ||
65 | + > | ||
66 | + {props.user.drones.map(drone => ( | ||
67 | + <MenuItem | ||
68 | + key={drone.id} | ||
69 | + value={drone.id} | ||
70 | + primaryText={`${drone.name} (${drone.model.name})`} | ||
71 | + /> | ||
72 | + ))} | ||
73 | + </DropDownMenu> | ||
74 | + <DatePicker | ||
75 | + floatingLabelText="날짜" | ||
76 | + onChange={this.handleChangeDate} | ||
77 | + defaultDate={state.date} | ||
78 | + minDate={minDate} | ||
79 | + maxDate={maxDate} | ||
80 | + /> | ||
81 | + <DropDownMenu | ||
82 | + maxHeight={300} | ||
83 | + value={state.area} | ||
84 | + onChange={this.handleChangeArea} | ||
85 | + > | ||
86 | + {props.orgs.map(org => ( | ||
87 | + <MenuItem key={org.id} value={org.name} primaryText={org.name} /> | ||
88 | + ))} | ||
89 | + </DropDownMenu> | ||
90 | + <TextField | ||
91 | + hintText="상세주소" | ||
92 | + type="text" | ||
93 | + value={state.address} | ||
94 | + onChange={this.handleChangeAddress} | ||
95 | + style={style} | ||
96 | + /> | ||
97 | + <RaisedButton | ||
98 | + label="확인" | ||
99 | + primary={true} | ||
100 | + style={style} | ||
101 | + onClick={() => props.createContract(state)} | ||
102 | + /> | ||
103 | + </div> | ||
104 | + ); | ||
105 | + } | ||
106 | +} | ||
107 | + | ||
108 | +export default RequestPage; |
1 | +import Component from "./Component"; | ||
2 | +import * as React from "react"; | ||
3 | +import { Query, Mutation } from "react-apollo"; | ||
4 | +import { withRouter, RouteComponentProps } from "react-router"; | ||
5 | + | ||
6 | +const FETCH_CURRENT_USER = require("./fetchCurrentUser.gql"); | ||
7 | +const CREATE_CONTRACT = require("./createContract.gql"); | ||
8 | + | ||
9 | +class FetchCurrentUserQuery extends Query<{ | ||
10 | + user: any; | ||
11 | + orgs: any; | ||
12 | +}> {} | ||
13 | + | ||
14 | +class CreateContractMutation extends Mutation<{}> {} | ||
15 | + | ||
16 | +const update = (history: any) => (cache, result) => history.push("/my-page"); | ||
17 | + | ||
18 | +const QueryComponent: React.SFC<RouteComponentProps<{}>> = ({ history }) => ( | ||
19 | + <FetchCurrentUserQuery query={FETCH_CURRENT_USER} fetchPolicy="network-only"> | ||
20 | + {({ loading, error, data }) => { | ||
21 | + if (loading) return <p>Loading...</p>; | ||
22 | + else if (error) return <p>Error :(</p>; | ||
23 | + else | ||
24 | + return ( | ||
25 | + <CreateContractMutation | ||
26 | + mutation={CREATE_CONTRACT} | ||
27 | + update={update(history)} | ||
28 | + > | ||
29 | + {mutate => { | ||
30 | + const createContract = input => mutate({ variables: { input } }); | ||
31 | + return ( | ||
32 | + <Component user={data!.user} orgs={data!.orgs} createContract={createContract} /> | ||
33 | + ); | ||
34 | + }} | ||
35 | + </CreateContractMutation> | ||
36 | + ); | ||
37 | + }} | ||
38 | + </FetchCurrentUserQuery> | ||
39 | +); | ||
40 | + | ||
41 | +export default withRouter(QueryComponent); |
1 | +import * as React from "react"; | ||
2 | +import { Link } from "react-router-dom"; | ||
3 | +import styled from "styled-components"; | ||
4 | +import TextField from "material-ui/TextField"; | ||
5 | +import RaisedButton from "material-ui/RaisedButton"; | ||
6 | + | ||
7 | +require("./styles.scss"); | ||
8 | + | ||
9 | +const style = { | ||
10 | + // width: "100%" | ||
11 | +}; | ||
12 | + | ||
13 | +interface Props { | ||
14 | + org: any; | ||
15 | +} | ||
16 | + | ||
17 | +class SearchPage extends React.Component<Props> { | ||
18 | + public render() { | ||
19 | + const { props, state } = this; | ||
20 | + const { org } = props; | ||
21 | + return ( | ||
22 | + <div className="judge-page"> | ||
23 | + <h2>신청서</h2> | ||
24 | + <ul> | ||
25 | + {org.contracts | ||
26 | + .filter(contract => contract.status === "ok") | ||
27 | + .map(contract => ( | ||
28 | + <li key={contract.id}> | ||
29 | + <h3>사유</h3> | ||
30 | + <p>{contract.reason}</p> | ||
31 | + <h3>주소</h3> | ||
32 | + <p>{contract.address}</p> | ||
33 | + <h3>드론</h3> | ||
34 | + <p> | ||
35 | + {contract.drone.name}({contract.drone.model.name}) | ||
36 | + </p> | ||
37 | + <br /> | ||
38 | + </li> | ||
39 | + ))} | ||
40 | + </ul> | ||
41 | + </div> | ||
42 | + ); | ||
43 | + } | ||
44 | +} | ||
45 | + | ||
46 | +export default SearchPage; |
1 | +import Component from "./Component"; | ||
2 | +import * as React from "react"; | ||
3 | +import { graphql, Query, Mutation } from "react-apollo"; | ||
4 | +import { withRouter, RouteComponentProps } from "react-router"; | ||
5 | + | ||
6 | +const FETCH_ORG = require("./fetchOrg.gql"); | ||
7 | + | ||
8 | +interface Props { | ||
9 | + org: any; | ||
10 | +} | ||
11 | + | ||
12 | +class FetchContractQuery extends Query<Props> {} | ||
13 | + | ||
14 | +const update = (history: any) => (cache, result) => history.goBack(); | ||
15 | + | ||
16 | +const QueryComponent: React.SFC<RouteComponentProps<{}> & Props> = props => ( | ||
17 | + <FetchContractQuery | ||
18 | + query={FETCH_ORG} | ||
19 | + fetchPolicy="network-only" | ||
20 | + variables={{ id: props.match.params["id"] }} | ||
21 | + > | ||
22 | + {({ loading, error, data }) => { | ||
23 | + if (loading) return <p>Loading...</p>; | ||
24 | + else if (error || !data!.org) return <p>Error :(</p>; | ||
25 | + else return <Component org={data!.org} />; | ||
26 | + }} | ||
27 | + </FetchContractQuery> | ||
28 | +); | ||
29 | + | ||
30 | +export default withRouter(QueryComponent); |
front/src/configureClient.ts
0 → 100644
1 | +import * as R from "ramda"; | ||
2 | +import { ApolloLink } from "apollo-link"; | ||
3 | +import { setContext } from "apollo-link-context"; | ||
4 | +import { InMemoryCache } from "apollo-cache-inmemory"; | ||
5 | +import { ApolloClient } from "apollo-client"; | ||
6 | +import { createUploadLink } from "apollo-upload-client"; | ||
7 | + | ||
8 | +const authLink = setContext((req, { headers }) => { | ||
9 | + const token = localStorage.getItem("token"); | ||
10 | + const authorization = token ? `Bearer ${token}` : ""; | ||
11 | + return { headers: R.merge(headers, { authorization }) }; | ||
12 | +}); | ||
13 | +const uploadLink = createUploadLink(); | ||
14 | + | ||
15 | +const link = ApolloLink.from([authLink, uploadLink]); | ||
16 | + | ||
17 | +const cache = new InMemoryCache({ | ||
18 | + cacheRedirects: { | ||
19 | + Query: {} | ||
20 | + } | ||
21 | +}); | ||
22 | + | ||
23 | +const client = new ApolloClient({ link, cache }); | ||
24 | + | ||
25 | +export default () => client; |
front/src/configureStore.ts
0 → 100644
File mode changed
front/src/index.scss
0 → 100644
1 | +body { | ||
2 | + margin: 0; | ||
3 | + padding: 0; | ||
4 | + font-family: sans-serif; | ||
5 | +} | ||
6 | + | ||
7 | +ul { | ||
8 | + padding: 0; | ||
9 | +} | ||
10 | + | ||
11 | +li { | ||
12 | + list-style-type: none; | ||
13 | +} | ||
14 | + | ||
15 | +a { | ||
16 | + color: #258fb8; | ||
17 | + text-decoration: none; | ||
18 | + &:hover { | ||
19 | + text-decoration: underline; | ||
20 | + } | ||
21 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
front/src/index.tsx
0 → 100644
1 | +import * as R from "ramda"; | ||
2 | +import * as React from "react"; | ||
3 | +import * as ReactDOM from "react-dom"; | ||
4 | + | ||
5 | +import { BrowserRouter } from "react-router-dom"; | ||
6 | +import { ApolloProvider } from "react-apollo"; | ||
7 | +import { Provider } from "react-redux"; | ||
8 | +import MuiThemeProvider from "material-ui/styles/MuiThemeProvider"; | ||
9 | + | ||
10 | +import App from "components/App"; | ||
11 | +// import configureStore from "./configureStore"; | ||
12 | +import configureClient from "./configureClient"; | ||
13 | + | ||
14 | +import registerServiceWorker from "./registerServiceWorker"; | ||
15 | +import "./index.scss"; | ||
16 | + | ||
17 | +// const store = configureStore(); | ||
18 | +const client = configureClient(); | ||
19 | + | ||
20 | +const Root = () => ( | ||
21 | + <ApolloProvider client={client}> | ||
22 | + <BrowserRouter> | ||
23 | + <MuiThemeProvider> | ||
24 | + <App /> | ||
25 | + </MuiThemeProvider> | ||
26 | + </BrowserRouter> | ||
27 | + </ApolloProvider> | ||
28 | +); | ||
29 | + | ||
30 | +ReactDOM.render(<Root />, document.getElementById("root") as HTMLElement); | ||
31 | + | ||
32 | +registerServiceWorker(); |
front/src/reducers/index.ts
0 → 100644
front/src/registerServiceWorker.ts
0 → 100644
1 | +// tslint:disable:no-console | ||
2 | +// In production, we register a service worker to serve assets from local cache. | ||
3 | + | ||
4 | +// This lets the app load faster on subsequent visits in production, and gives | ||
5 | +// it offline capabilities. However, it also means that developers (and users) | ||
6 | +// will only see deployed updates on the 'N+1' visit to a page, since previously | ||
7 | +// cached resources are updated in the background. | ||
8 | + | ||
9 | +// To learn more about the benefits of this model, read https://goo.gl/KwvDNy. | ||
10 | +// This link also includes instructions on opting out of this behavior. | ||
11 | + | ||
12 | +const isLocalhost = Boolean( | ||
13 | + window.location.hostname === 'localhost' || | ||
14 | + // [::1] is the IPv6 localhost address. | ||
15 | + window.location.hostname === '[::1]' || | ||
16 | + // 127.0.0.1/8 is considered localhost for IPv4. | ||
17 | + window.location.hostname.match( | ||
18 | + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ | ||
19 | + ) | ||
20 | +); | ||
21 | + | ||
22 | +export default function register() { | ||
23 | + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { | ||
24 | + // The URL constructor is available in all browsers that support SW. | ||
25 | + const publicUrl = new URL( | ||
26 | + process.env.PUBLIC_URL!, | ||
27 | + window.location.toString() | ||
28 | + ); | ||
29 | + if (publicUrl.origin !== window.location.origin) { | ||
30 | + // Our service worker won't work if PUBLIC_URL is on a different origin | ||
31 | + // from what our page is served on. This might happen if a CDN is used to | ||
32 | + // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 | ||
33 | + return; | ||
34 | + } | ||
35 | + | ||
36 | + window.addEventListener('load', () => { | ||
37 | + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; | ||
38 | + | ||
39 | + if (isLocalhost) { | ||
40 | + // This is running on localhost. Lets check if a service worker still exists or not. | ||
41 | + checkValidServiceWorker(swUrl); | ||
42 | + | ||
43 | + // Add some additional logging to localhost, pointing developers to the | ||
44 | + // service worker/PWA documentation. | ||
45 | + navigator.serviceWorker.ready.then(() => { | ||
46 | + console.log( | ||
47 | + 'This web app is being served cache-first by a service ' + | ||
48 | + 'worker. To learn more, visit https://goo.gl/SC7cgQ' | ||
49 | + ); | ||
50 | + }); | ||
51 | + } else { | ||
52 | + // Is not local host. Just register service worker | ||
53 | + registerValidSW(swUrl); | ||
54 | + } | ||
55 | + }); | ||
56 | + } | ||
57 | +} | ||
58 | + | ||
59 | +function registerValidSW(swUrl: string) { | ||
60 | + navigator.serviceWorker | ||
61 | + .register(swUrl) | ||
62 | + .then(registration => { | ||
63 | + registration.onupdatefound = () => { | ||
64 | + const installingWorker = registration.installing; | ||
65 | + if (installingWorker) { | ||
66 | + installingWorker.onstatechange = () => { | ||
67 | + if (installingWorker.state === 'installed') { | ||
68 | + if (navigator.serviceWorker.controller) { | ||
69 | + // At this point, the old content will have been purged and | ||
70 | + // the fresh content will have been added to the cache. | ||
71 | + // It's the perfect time to display a 'New content is | ||
72 | + // available; please refresh.' message in your web app. | ||
73 | + console.log('New content is available; please refresh.'); | ||
74 | + } else { | ||
75 | + // At this point, everything has been precached. | ||
76 | + // It's the perfect time to display a | ||
77 | + // 'Content is cached for offline use.' message. | ||
78 | + console.log('Content is cached for offline use.'); | ||
79 | + } | ||
80 | + } | ||
81 | + }; | ||
82 | + } | ||
83 | + }; | ||
84 | + }) | ||
85 | + .catch(error => { | ||
86 | + console.error('Error during service worker registration:', error); | ||
87 | + }); | ||
88 | +} | ||
89 | + | ||
90 | +function checkValidServiceWorker(swUrl: string) { | ||
91 | + // Check if the service worker can be found. If it can't reload the page. | ||
92 | + fetch(swUrl) | ||
93 | + .then(response => { | ||
94 | + // Ensure service worker exists, and that we really are getting a JS file. | ||
95 | + if ( | ||
96 | + response.status === 404 || | ||
97 | + response.headers.get('content-type')!.indexOf('javascript') === -1 | ||
98 | + ) { | ||
99 | + // No service worker found. Probably a different app. Reload the page. | ||
100 | + navigator.serviceWorker.ready.then(registration => { | ||
101 | + registration.unregister().then(() => { | ||
102 | + window.location.reload(); | ||
103 | + }); | ||
104 | + }); | ||
105 | + } else { | ||
106 | + // Service worker found. Proceed as normal. | ||
107 | + registerValidSW(swUrl); | ||
108 | + } | ||
109 | + }) | ||
110 | + .catch(() => { | ||
111 | + console.log( | ||
112 | + 'No internet connection found. App is running in offline mode.' | ||
113 | + ); | ||
114 | + }); | ||
115 | +} | ||
116 | + | ||
117 | +export function unregister() { | ||
118 | + if ('serviceWorker' in navigator) { | ||
119 | + navigator.serviceWorker.ready.then(registration => { | ||
120 | + registration.unregister(); | ||
121 | + }); | ||
122 | + } | ||
123 | +} |
front/tsconfig.json
0 → 100644
1 | +{ | ||
2 | + "compilerOptions": { | ||
3 | + "outDir": "build/dist", | ||
4 | + "module": "esnext", | ||
5 | + "target": "es5", | ||
6 | + "lib": [ | ||
7 | + "es6", | ||
8 | + "dom", | ||
9 | + "esnext.asynciterable" | ||
10 | + ], | ||
11 | + "sourceMap": true, | ||
12 | + "allowJs": true, | ||
13 | + "jsx": "react", | ||
14 | + "moduleResolution": "node", | ||
15 | + "rootDir": "src", | ||
16 | + "forceConsistentCasingInFileNames": true, | ||
17 | + "noImplicitReturns": true, | ||
18 | + "noImplicitThis": true, | ||
19 | + "noImplicitAny": false, | ||
20 | + "strictNullChecks": true, | ||
21 | + "suppressImplicitAnyIndexErrors": true, | ||
22 | + "noUnusedLocals": false, | ||
23 | + "baseUrl": "./src" | ||
24 | + }, | ||
25 | + "exclude": [ | ||
26 | + "../node_modules", | ||
27 | + "build", | ||
28 | + "scripts", | ||
29 | + "acceptance-tests", | ||
30 | + "webpack", | ||
31 | + "jest", | ||
32 | + "src/setupTests.ts" | ||
33 | + ] | ||
34 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
front/tsconfig.test.json
0 → 100644
front/tslint.json
0 → 100644
readme.md
0 → 100644
1 | +nodejs 10.4.1 | ||
2 | +yarn 1.6.0 | ||
3 | +oracle DB // username: "system", password: "oracle", database: "khu"로 로그인 할 수 있어야 하고 테이블이 없어야함(두 번째 실행시 configureDB.ts synchronize를 false로 주지 않으면 에러발생) | ||
4 | + | ||
5 | +front와 server에서 각각 yarn install을 입력해서 의존성을 받음 | ||
6 | +완료시 각각 yarn start로 개발서버를 띄울 수 있음 | ||
7 | +빌드가 잘 되지 않으면 node-sass, oracledb, typescript, babel, webpack을 yarn global add {패키지 이름}을 입력해서 전역으로 설치 | ||
8 | + | ||
9 | +server를 먼저 띄우고 front를 띄우면 front의 package.json의 proxy를 참조해서 server에 연결됨(이 부분에서 문제가 발생하면 화면은 나오지만 통신을 못함) | ||
10 | +위와 같은 문제 발생시 front의 proxy를 localhost, 0.0.0.0, 진짜 IP등으로 바꿔가면서 실행 | ||
11 | + | ||
12 | +모든 과정이 준비되면 | ||
13 | +localhost:3000으로 접속해서 시연 가능 | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
server/assets/img/Battlecruiser.jpg
0 → 100644

7.26 KB
server/assets/img/F15.jpg
0 → 100644

141 KB
server/assets/img/F20.jpg
0 → 100644

84.4 KB
server/assets/img/F22.jpg
0 → 100644

47.9 KB
server/assets/img/Raptor.jpg
0 → 100644

81.6 KB
server/assets/img/Valkyrie.jpg
0 → 100644

10.2 KB
server/config/env.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +const fs = require('fs'); | ||
4 | +const path = require('path'); | ||
5 | +const paths = require('./paths'); | ||
6 | + | ||
7 | +delete require.cache[require.resolve('./paths')]; | ||
8 | + | ||
9 | +const NODE_ENV = process.env.NODE_ENV; | ||
10 | +if (!NODE_ENV) { | ||
11 | + throw new Error( | ||
12 | + 'The NODE_ENV environment variable is required but was not specified.' | ||
13 | + ); | ||
14 | +} | ||
15 | + | ||
16 | +var dotenvFiles = [ | ||
17 | + `${paths.dotenv}.${NODE_ENV}.local`, | ||
18 | + `${paths.dotenv}.${NODE_ENV}`, | ||
19 | + NODE_ENV !== 'test' && `${paths.dotenv}.local`, | ||
20 | + paths.dotenv, | ||
21 | +].filter(Boolean); | ||
22 | + | ||
23 | +dotenvFiles.forEach(dotenvFile => { | ||
24 | + if (fs.existsSync(dotenvFile)) { | ||
25 | + require('dotenv-expand')( | ||
26 | + require('dotenv').config({ | ||
27 | + path: dotenvFile, | ||
28 | + }) | ||
29 | + ); | ||
30 | + } | ||
31 | +}); | ||
32 | + | ||
33 | +const appDirectory = fs.realpathSync(process.cwd()); | ||
34 | +process.env.NODE_PATH = (process.env.NODE_PATH || '') | ||
35 | + .split(path.delimiter) | ||
36 | + .filter(folder => folder && !path.isAbsolute(folder)) | ||
37 | + .map(folder => path.resolve(appDirectory, folder)) | ||
38 | + .join(path.delimiter); | ||
39 | + | ||
40 | +const REACT_APP = /^REACT_APP_/i; | ||
41 | + | ||
42 | +function getClientEnvironment(publicUrl) { | ||
43 | + const raw = Object.keys(process.env) | ||
44 | + .filter(key => REACT_APP.test(key)) | ||
45 | + .reduce( | ||
46 | + (env, key) => { | ||
47 | + env[key] = process.env[key]; | ||
48 | + return env; | ||
49 | + }, | ||
50 | + { | ||
51 | + NODE_ENV: process.env.NODE_ENV || 'development', | ||
52 | + PUBLIC_URL: publicUrl, | ||
53 | + } | ||
54 | + ); | ||
55 | + const stringified = { | ||
56 | + 'process.env': Object.keys(raw).reduce((env, key) => { | ||
57 | + env[key] = JSON.stringify(raw[key]); | ||
58 | + return env; | ||
59 | + }, {}), | ||
60 | + }; | ||
61 | + | ||
62 | + return { raw, stringified }; | ||
63 | +} | ||
64 | + | ||
65 | +module.exports = getClientEnvironment; |
server/config/paths.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +const path = require('path'); | ||
4 | +const fs = require('fs'); | ||
5 | +const url = require('url'); | ||
6 | + | ||
7 | +const appDirectory = fs.realpathSync(process.cwd()); | ||
8 | +const resolveApp = relativePath => path.resolve(appDirectory, relativePath); | ||
9 | + | ||
10 | +const envPublicUrl = process.env.PUBLIC_URL; | ||
11 | + | ||
12 | +function ensureSlash(path, needsSlash) { | ||
13 | + const hasSlash = path.endsWith('/'); | ||
14 | + if (hasSlash && !needsSlash) { | ||
15 | + return path.substr(path, path.length - 1); | ||
16 | + } else if (!hasSlash && needsSlash) { | ||
17 | + return `${path}/`; | ||
18 | + } else { | ||
19 | + return path; | ||
20 | + } | ||
21 | +} | ||
22 | + | ||
23 | +const getPublicUrl = appPackageJson => | ||
24 | + envPublicUrl || require(appPackageJson).homepage; | ||
25 | + | ||
26 | +function getServedPath(appPackageJson) { | ||
27 | + const publicUrl = getPublicUrl(appPackageJson); | ||
28 | + const servedUrl = | ||
29 | + envPublicUrl || (publicUrl ? url.parse(publicUrl).pathname : '/'); | ||
30 | + return ensureSlash(servedUrl, true); | ||
31 | +} | ||
32 | + | ||
33 | +module.exports = { | ||
34 | + appPackageJson: resolveApp('package.json'), | ||
35 | + appTsConfig: resolveApp('tsconfig.json'), | ||
36 | + appTsLint: resolveApp('tslint.json'), | ||
37 | + | ||
38 | + yarnLockFile: resolveApp('yarn.lock'), | ||
39 | + testsSetup: resolveApp('src/setupTests.js'), | ||
40 | + appNodeModules: resolveApp('node_modules'), | ||
41 | + publicUrl: getPublicUrl(resolveApp('package.json')), | ||
42 | + servedPath: getServedPath(resolveApp('package.json')), | ||
43 | + | ||
44 | + dotenv: resolveApp('.env'), | ||
45 | + serverAssets: resolveApp('assets'), | ||
46 | + appSrc: resolveApp('src'), | ||
47 | + appLib: resolveApp('lib'), | ||
48 | + appEntry: resolveApp('src/index.ts'), | ||
49 | + appBuild: resolveApp('../build/server'), | ||
50 | + temporalOutput: resolveApp('tmp'), | ||
51 | +}; |
server/config/webpack.config.dev.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +const webpack = require('webpack'); | ||
4 | +const paths = require('./paths'); | ||
5 | +const nodeExternals = require('webpack-node-externals'); | ||
6 | +const CopyWebpackPlugin = require('copy-webpack-plugin'); | ||
7 | +const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); | ||
8 | +const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); | ||
9 | + | ||
10 | +module.exports = { | ||
11 | + devtool: 'cheap-module-source-map', | ||
12 | + target: 'node', | ||
13 | + entry: paths.appEntry, | ||
14 | + output: { | ||
15 | + path: paths.temporalOutput, | ||
16 | + filename: 'index.js' | ||
17 | + }, | ||
18 | + externals: [nodeExternals()], | ||
19 | + resolve: { | ||
20 | + modules: [paths.appLib, paths.appSrc, paths.appNodeModules], | ||
21 | + extensions: [ | ||
22 | + '.mjs', | ||
23 | + '.web.ts', | ||
24 | + '.ts', | ||
25 | + '.web.tsx', | ||
26 | + '.tsx', | ||
27 | + '.web.js', | ||
28 | + '.js', | ||
29 | + '.json', | ||
30 | + '.web.jsx', | ||
31 | + '.jsx', | ||
32 | + ], | ||
33 | + plugins: [ | ||
34 | + new TsconfigPathsPlugin({ configFile: paths.appTsConfig }), | ||
35 | + ], | ||
36 | + }, | ||
37 | + module: { | ||
38 | + strictExportPresence: true, | ||
39 | + rules: [ | ||
40 | + { | ||
41 | + test: /\.(js|jsx|mjs)$/, | ||
42 | + loader: require.resolve('source-map-loader'), | ||
43 | + enforce: 'pre', | ||
44 | + include: paths.appSrc, | ||
45 | + }, | ||
46 | + { | ||
47 | + oneOf: [ | ||
48 | + { | ||
49 | + test: /\.(js|jsx|mjs)$/, | ||
50 | + loader: require.resolve('babel-loader'), | ||
51 | + options: { | ||
52 | + cacheDirectory: true, | ||
53 | + }, | ||
54 | + }, | ||
55 | + { | ||
56 | + test: /\.(ts|tsx)$/, | ||
57 | + include: paths.appSrc, | ||
58 | + use: [ | ||
59 | + { | ||
60 | + loader: require.resolve('ts-loader'), | ||
61 | + options: { | ||
62 | + transpileOnly: true, | ||
63 | + }, | ||
64 | + }, | ||
65 | + ], | ||
66 | + }, | ||
67 | + { | ||
68 | + test: /\.(graphql|gql)$/, | ||
69 | + loader: require.resolve('graphql-tag/loader'), | ||
70 | + } | ||
71 | + ], | ||
72 | + }, | ||
73 | + ], | ||
74 | + }, | ||
75 | + plugins: [ | ||
76 | + new webpack.BannerPlugin({ banner: 'require("source-map-support").install();', raw: true, entryOnly: false }), | ||
77 | + new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), | ||
78 | + new webpack.DefinePlugin({ | ||
79 | + 'process.env.NODE_ENV': '"development"', | ||
80 | + '__DEV__': true | ||
81 | + }), | ||
82 | + new ForkTsCheckerWebpackPlugin({ | ||
83 | + async: false, | ||
84 | + watch: paths.appSrc, | ||
85 | + tsconfig: paths.appTsConfig, | ||
86 | + tslint: paths.appTsLint, | ||
87 | + }), | ||
88 | + new CopyWebpackPlugin([ | ||
89 | + { from: paths.serverAssets, to: 'assets' }, | ||
90 | + ]) | ||
91 | + ], | ||
92 | + devtool: 'sourcemap' | ||
93 | +}; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
server/config/webpack.config.prod.js
0 → 100644
1 | +const webpack = require('webpack'); | ||
2 | +const paths = require('./paths'); | ||
3 | +const nodeExternals = require('webpack-node-externals'); | ||
4 | +const CopyWebpackPlugin = require('copy-webpack-plugin') | ||
5 | + | ||
6 | +module.exports = { | ||
7 | + target: 'node', | ||
8 | + entry: paths.appEntry, | ||
9 | + output: { | ||
10 | + path: paths.appBuild, | ||
11 | + filename: 'index.js' | ||
12 | + }, | ||
13 | + externals: [nodeExternals()], | ||
14 | + resolve: { | ||
15 | + modules: [paths.appLib, paths.appSrc, paths.appNodeModules], | ||
16 | + extensions: [ | ||
17 | + '.mjs', | ||
18 | + '.web.ts', | ||
19 | + '.ts', | ||
20 | + '.web.tsx', | ||
21 | + '.tsx', | ||
22 | + '.web.js', | ||
23 | + '.js', | ||
24 | + '.json', | ||
25 | + '.web.jsx', | ||
26 | + '.jsx', | ||
27 | + ], | ||
28 | + }, | ||
29 | + module: { | ||
30 | + strictExportPresence: true, | ||
31 | + rules: [ | ||
32 | + { | ||
33 | + oneOf: [ | ||
34 | + { | ||
35 | + test: /\.(js|jsx|mjs)$/, | ||
36 | + loader: require.resolve('babel-loader'), | ||
37 | + options: { | ||
38 | + cacheDirectory: true, | ||
39 | + }, | ||
40 | + }, | ||
41 | + { | ||
42 | + test: /\.(ts|tsx)$/, | ||
43 | + include: paths.appSrc, | ||
44 | + use: [ | ||
45 | + { | ||
46 | + loader: require.resolve('ts-loader'), | ||
47 | + options: { | ||
48 | + transpileOnly: true, | ||
49 | + }, | ||
50 | + }, | ||
51 | + ], | ||
52 | + }, | ||
53 | + { | ||
54 | + test: /\.(graphql|gql)$/, | ||
55 | + loader: require.resolve('graphql-tag/loader'), | ||
56 | + } | ||
57 | + ], | ||
58 | + }, | ||
59 | + ], | ||
60 | + }, | ||
61 | + plugins: [ | ||
62 | + new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), | ||
63 | + new webpack.DefinePlugin({ | ||
64 | + 'process.env.NODE_ENV': '"production"', | ||
65 | + '__DEV__': false | ||
66 | + }), | ||
67 | + new webpack.optimize.UglifyJsPlugin({ | ||
68 | + compress: { | ||
69 | + warnings: false, | ||
70 | + comparisons: false, | ||
71 | + }, | ||
72 | + mangle: { | ||
73 | + safari10: true, | ||
74 | + }, | ||
75 | + output: { | ||
76 | + comments: false, | ||
77 | + ascii_only: true, | ||
78 | + }, | ||
79 | + }), | ||
80 | + new CopyWebpackPlugin([ | ||
81 | + { from: paths.serverAssets, to: 'assets' }, | ||
82 | + ]) | ||
83 | + ], | ||
84 | + devtool: 'sourcemap' | ||
85 | +}; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
server/package.json
0 → 100644
1 | +{ | ||
2 | + "name": "server", | ||
3 | + "version": "1.0.0", | ||
4 | + "main": "index.js", | ||
5 | + "license": "MIT", | ||
6 | + "scripts": { | ||
7 | + "start": "node scripts/start.js", | ||
8 | + "build": "node scripts/build.js", | ||
9 | + "post": "ts-node --project scripts/tsconfig.json scripts/post.ts", | ||
10 | + "sync": "ts-node --project scripts/tsconfig.json scripts/sync.ts" | ||
11 | + }, | ||
12 | + "devDependencies": { | ||
13 | + "@types/compression": "^0.0.36", | ||
14 | + "@types/express": "^4.11.1", | ||
15 | + "@types/formidable": "^1.0.31", | ||
16 | + "@types/fs-extra": "^5.0.2", | ||
17 | + "@types/graphql": "^0.12.5", | ||
18 | + "@types/morgan": "^1.7.35", | ||
19 | + "@types/ramda": "^0.25.21", | ||
20 | + "@types/redux-actions": "^2.2.4", | ||
21 | + "@types/sequelize": "^4.27.10", | ||
22 | + "babel-core": "^6.26.0", | ||
23 | + "babel-loader": "^7.1.2", | ||
24 | + "babel-plugin-transform-class-properties": "^6.24.1", | ||
25 | + "babel-plugin-transform-decorators-legacy": "^1.3.4", | ||
26 | + "babel-preset-env": "^1.6.1", | ||
27 | + "babel-preset-stage-0": "^6.24.1", | ||
28 | + "chokidar": "^2.0.2", | ||
29 | + "copy-webpack-plugin": "^4.5.1", | ||
30 | + "fork-ts-checker-webpack-plugin": "^0.4.1", | ||
31 | + "graphql-tag": "^2.7.3", | ||
32 | + "nodemon": "^1.15.0", | ||
33 | + "source-map-loader": "^0.2.3", | ||
34 | + "source-map-support": "^0.5.3", | ||
35 | + "start-server-webpack-plugin": "^2.2.5", | ||
36 | + "ts-loader": "2.3.7", | ||
37 | + "tsconfig-paths-webpack-plugin": "^3.0.2", | ||
38 | + "tslint": "^5.9.1", | ||
39 | + "typescript": "^2.7.2", | ||
40 | + "webpack": "^3.11.0", | ||
41 | + "webpack-node-externals": "^1.6.0" | ||
42 | + }, | ||
43 | + "dependencies": { | ||
44 | + "apollo-server-express": "^1.3.2", | ||
45 | + "apollo-upload-server": "^5.0.0", | ||
46 | + "body-parser": "^1.18.2", | ||
47 | + "compression": "^1.7.2", | ||
48 | + "express": "^4.16.2", | ||
49 | + "formidable": "^1.2.1", | ||
50 | + "fs-extra": "^5.0.0", | ||
51 | + "graphql": "0.12", | ||
52 | + "graphql-tools": "^2.21.0", | ||
53 | + "helmet": "^3.11.0", | ||
54 | + "jsonwebtoken": "^8.2.1", | ||
55 | + "morgan": "^1.9.0", | ||
56 | + "node-fetch": "^2.1.1", | ||
57 | + "oracledb": "^2.3.0", | ||
58 | + "ramda": "^0.25.0", | ||
59 | + "serve-favicon": "^2.5.0", | ||
60 | + "typeorm": "^0.1.16" | ||
61 | + } | ||
62 | +} |
server/scripts/build.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +process.env.BABEL_ENV = 'production'; | ||
4 | +process.env.NODE_ENV = 'production'; | ||
5 | + | ||
6 | +process.on('unhandledRejection', (err) => { throw err; }); | ||
7 | + | ||
8 | +const webpack = require('webpack'); | ||
9 | +const config = require('../config/webpack.config.prod'); | ||
10 | +const paths = require('../config/paths'); | ||
11 | +const fs = require('fs-extra'); | ||
12 | + | ||
13 | +fs.emptyDirSync(paths.appBuild); | ||
14 | +const compiler = webpack(config); | ||
15 | +compiler.run((err, stats) => { console.log(stats.compilation.errors) }); | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
server/scripts/start.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +process.env.BABEL_ENV = 'development'; | ||
4 | +process.env.NODE_ENV = 'development'; | ||
5 | + | ||
6 | +process.on('unhandledRejection', (err) => { throw err; }); | ||
7 | + | ||
8 | +const fs = require('fs-extra'); | ||
9 | +const webpack = require('webpack'); | ||
10 | +const nodemon = require('nodemon'); | ||
11 | +const config = require('../config/webpack.config.dev'); | ||
12 | +const paths = require('../config/paths'); | ||
13 | + | ||
14 | +const compiler = webpack(config); | ||
15 | + | ||
16 | +fs.emptyDirSync(paths.temporalOutput); | ||
17 | + | ||
18 | +const run = () => new Promise((res, rej) => { | ||
19 | + compiler.watch({}, (err, stats) => { | ||
20 | + if (err) | ||
21 | + rej(err) | ||
22 | + else | ||
23 | + res(stats); | ||
24 | + }); | ||
25 | +}) | ||
26 | + | ||
27 | +async function go() { | ||
28 | + await run(); | ||
29 | + nodemon({ | ||
30 | + script: `${paths.temporalOutput}/index.js`, | ||
31 | + ext: 'js json' | ||
32 | + }); | ||
33 | +} | ||
34 | + | ||
35 | +go(); | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
server/scripts/tsconfig.json
0 → 100644
1 | +{ | ||
2 | + "compilerOptions": { | ||
3 | + "module": "commonjs", | ||
4 | + "target": "es5", | ||
5 | + "lib": [ | ||
6 | + "es6", | ||
7 | + "dom" | ||
8 | + ], | ||
9 | + "sourceMap": true, | ||
10 | + "allowJs": true, | ||
11 | + "jsx": "react", | ||
12 | + "moduleResolution": "node", | ||
13 | + "forceConsistentCasingInFileNames": true, | ||
14 | + "noImplicitReturns": true, | ||
15 | + "noImplicitThis": true, | ||
16 | + "emitDecoratorMetadata": true, | ||
17 | + "experimentalDecorators": true, | ||
18 | + "noImplicitAny": false, | ||
19 | + "strictNullChecks": true, | ||
20 | + "suppressImplicitAnyIndexErrors": true, | ||
21 | + "noUnusedLocals": false, | ||
22 | + "baseUrl": "../", | ||
23 | + "paths": { | ||
24 | + "*": [ | ||
25 | + "node_modules/*", | ||
26 | + "src/*", | ||
27 | + "lib/*" | ||
28 | + ] | ||
29 | + } | ||
30 | + }, | ||
31 | + "exclude": [ | ||
32 | + "node_modules", | ||
33 | + "tmp" | ||
34 | + ] | ||
35 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
server/src/configureApp/configureDB.ts
0 → 100644
1 | +import { Application } from "express"; | ||
2 | +import { ConnectionOptions, createConnection } from "typeorm"; | ||
3 | + | ||
4 | +import Contract from "entity/Contract"; | ||
5 | +import Drone from "entity/Drone"; | ||
6 | +import Model from "entity/Model"; | ||
7 | +import Org from "entity/Org"; | ||
8 | +import User from "entity/User"; | ||
9 | +import Dataset from "entity/Dataset"; | ||
10 | + | ||
11 | +import fake from "../fake"; | ||
12 | + | ||
13 | +const options: ConnectionOptions = { | ||
14 | + type: "oracle", | ||
15 | + host: "localhost", | ||
16 | + port: 1521, | ||
17 | + username: "system", | ||
18 | + password: "oracle", | ||
19 | + database: "khu", | ||
20 | + logger: "debug", | ||
21 | + synchronize: true, | ||
22 | + entities: [Contract, Drone, Model, Org, User, Dataset] | ||
23 | +}; | ||
24 | + | ||
25 | +export default async (app: Application) => { | ||
26 | + try { | ||
27 | + const connection = await createConnection(options); | ||
28 | + await fake(connection); | ||
29 | + Object.assign(app.locals, { connection }); | ||
30 | + } catch (e) { | ||
31 | + console.log(e); | ||
32 | + } | ||
33 | +}; |
server/src/configureApp/index.ts
0 → 100644
1 | +import * as express from "express"; | ||
2 | +import { Application, RequestHandler } from "express"; | ||
3 | +import * as fs from "fs-extra"; | ||
4 | +import * as morgan from "morgan"; | ||
5 | +import * as helmet from "helmet"; | ||
6 | +import * as compression from "compression"; | ||
7 | + | ||
8 | +import graphql from "routers/graphql"; | ||
9 | +import graphiql from "routers/graphiql"; | ||
10 | +import configureDB from "./configureDB"; | ||
11 | + | ||
12 | +export default async (app: Application) => { | ||
13 | + Object.assign(app.locals, { port: 7777 }); | ||
14 | + await configureDB(app); | ||
15 | + app.use(morgan("dev")); | ||
16 | + // app.use("/assets", express.static("assets")); | ||
17 | + app.use("/graphql", graphql); | ||
18 | + // app.use("/graphiql", graphiql); | ||
19 | + | ||
20 | + app.listen(app.locals.port, () => | ||
21 | + console.log(`listening ${app.locals.port}`) | ||
22 | + ); | ||
23 | +}; |
server/src/entity/Contract.ts
0 → 100644
1 | +import { | ||
2 | + Entity, | ||
3 | + CreateDateColumn, | ||
4 | + PrimaryGeneratedColumn, | ||
5 | + Column, | ||
6 | + ManyToMany, | ||
7 | + ManyToOne, | ||
8 | + EntityOptions, | ||
9 | + OneToMany, | ||
10 | + OneToOne | ||
11 | +} from "typeorm"; | ||
12 | + | ||
13 | +import User from "./User"; | ||
14 | +import Drone from "./Drone"; | ||
15 | +import Org from "./Org"; | ||
16 | + | ||
17 | +@Entity("contracts") | ||
18 | +export default class Contract { | ||
19 | + @PrimaryGeneratedColumn() id: string; | ||
20 | + @Column() reason: string; | ||
21 | + @Column() date: string; | ||
22 | + @Column() area: string; | ||
23 | + @Column() address: string; | ||
24 | + @Column() status: string; | ||
25 | + @Column() review: string; | ||
26 | + | ||
27 | + @ManyToOne(type => User) | ||
28 | + writer: User; | ||
29 | + | ||
30 | + @ManyToOne(type => Drone) | ||
31 | + drone: Drone; | ||
32 | + | ||
33 | + @ManyToOne(type => Org) | ||
34 | + org: Org; | ||
35 | +} |
server/src/entity/Dataset.ts
0 → 100644
1 | +import { | ||
2 | + Entity, | ||
3 | + CreateDateColumn, | ||
4 | + PrimaryGeneratedColumn, | ||
5 | + Column, | ||
6 | + ManyToMany, | ||
7 | + ManyToOne, | ||
8 | + EntityOptions, | ||
9 | + OneToMany, | ||
10 | + OneToOne | ||
11 | +} from "typeorm"; | ||
12 | + | ||
13 | +import User from "./User"; | ||
14 | +import Drone from "./Drone"; | ||
15 | +import Org from "./Org"; | ||
16 | + | ||
17 | +@Entity("datasets") | ||
18 | +export default class Dataset { | ||
19 | + @PrimaryGeneratedColumn() id: string; | ||
20 | + @Column() comment: string; | ||
21 | + | ||
22 | + @ManyToOne(type => Drone) | ||
23 | + producer: Drone; | ||
24 | + | ||
25 | + @ManyToMany(type => User, user => user.datasets) | ||
26 | + buyers: Array<User>; | ||
27 | +} |
server/src/entity/Drone.ts
0 → 100644
1 | +import { | ||
2 | + Entity, | ||
3 | + CreateDateColumn, | ||
4 | + PrimaryGeneratedColumn, | ||
5 | + Column, | ||
6 | + ManyToMany, | ||
7 | + ManyToOne, | ||
8 | + EntityOptions, | ||
9 | + OneToMany, | ||
10 | + OneToOne | ||
11 | +} from "typeorm"; | ||
12 | + | ||
13 | +import User from "./User"; | ||
14 | +import Model from "./Model"; | ||
15 | +import Dataset from "./Dataset"; | ||
16 | + | ||
17 | +@Entity("drones") | ||
18 | +export default class Drone { | ||
19 | + @PrimaryGeneratedColumn() id: string; | ||
20 | + @Column() date: string; | ||
21 | + @Column() name: string; | ||
22 | + | ||
23 | + @ManyToOne(type => User) | ||
24 | + owner: User; | ||
25 | + | ||
26 | + @ManyToOne(type => Model) | ||
27 | + model: Model; | ||
28 | + | ||
29 | + @OneToMany(type => Dataset, dataset => dataset.producer) | ||
30 | + datasets: Dataset; | ||
31 | +} |
server/src/entity/Model.ts
0 → 100644
1 | +import { | ||
2 | + Entity, | ||
3 | + CreateDateColumn, | ||
4 | + PrimaryGeneratedColumn, | ||
5 | + Column, | ||
6 | + ManyToMany, | ||
7 | + ManyToOne, | ||
8 | + EntityOptions, | ||
9 | + OneToMany, | ||
10 | + OneToOne | ||
11 | +} from "typeorm"; | ||
12 | + | ||
13 | +import Drone from "./Drone"; | ||
14 | + | ||
15 | +@Entity("models") | ||
16 | +export default class Model { | ||
17 | + @PrimaryGeneratedColumn() id: string; | ||
18 | + @Column() name: string; | ||
19 | +} |
server/src/entity/Org.ts
0 → 100644
1 | +import { | ||
2 | + Entity, | ||
3 | + CreateDateColumn, | ||
4 | + PrimaryGeneratedColumn, | ||
5 | + Column, | ||
6 | + ManyToMany, | ||
7 | + ManyToOne, | ||
8 | + EntityOptions, | ||
9 | + OneToMany, | ||
10 | + OneToOne | ||
11 | +} from "typeorm"; | ||
12 | + | ||
13 | +import Contract from "./Contract"; | ||
14 | + | ||
15 | +@Entity("orgs") | ||
16 | +export default class Org { | ||
17 | + @PrimaryGeneratedColumn() | ||
18 | + id: string; | ||
19 | + | ||
20 | + @Column() | ||
21 | + name: string; | ||
22 | + | ||
23 | + @OneToMany(type => Contract, contract => contract.org) | ||
24 | + contracts: Array<Contract>; | ||
25 | +} |
server/src/entity/User.ts
0 → 100644
1 | +import { | ||
2 | + Entity, | ||
3 | + CreateDateColumn, | ||
4 | + PrimaryGeneratedColumn, | ||
5 | + Column, | ||
6 | + ManyToMany, | ||
7 | + JoinTable, | ||
8 | + ManyToOne, | ||
9 | + EntityOptions, | ||
10 | + OneToMany, | ||
11 | + OneToOne | ||
12 | +} from "typeorm"; | ||
13 | + | ||
14 | +import Dataset from "./Dataset"; | ||
15 | +import Drone from "./Drone"; | ||
16 | +import Contract from "./Contract"; | ||
17 | + | ||
18 | +@Entity("users") | ||
19 | +export default class User { | ||
20 | + @PrimaryGeneratedColumn() id: string; | ||
21 | + @Column() email: string; | ||
22 | + @Column() password: string; | ||
23 | + | ||
24 | + @OneToMany(type => Drone, drone => drone.owner) | ||
25 | + drones: Array<Drone>; | ||
26 | + | ||
27 | + @OneToMany(type => Contract, contract => contract.writer) | ||
28 | + contracts: Array<Contract>; | ||
29 | + | ||
30 | + @ManyToMany(type => Dataset, dataset => dataset.buyers) | ||
31 | + @JoinTable() | ||
32 | + datasets: Array<Dataset>; | ||
33 | +} |
server/src/fake.ts
0 → 100644
1 | +import { Connection } from "typeorm"; | ||
2 | +import User from "entity/User"; | ||
3 | +import Org from "entity/Org"; | ||
4 | +import Model from "entity/Model"; | ||
5 | + | ||
6 | +export default async function fake(connection: Connection) { | ||
7 | + const userRepo = connection.getRepository(User); | ||
8 | + const result = await userRepo.find(); | ||
9 | + console.log(result); | ||
10 | + if (result.length > 0) { | ||
11 | + console.log("NOT FIRST.. END FAKE"); | ||
12 | + return; | ||
13 | + } | ||
14 | + | ||
15 | + try { | ||
16 | + console.log("FIRST.. FAKE START"); | ||
17 | + let user = new User(); | ||
18 | + user.email = "db@khu.ac.kr"; | ||
19 | + user.password = "db"; | ||
20 | + await userRepo.save(user); | ||
21 | + | ||
22 | + console.log("USER DONE"); | ||
23 | + | ||
24 | + let modelA = new Model(); | ||
25 | + modelA.name = "F15"; | ||
26 | + | ||
27 | + let modelB = new Model(); | ||
28 | + modelB.name = "F20"; | ||
29 | + | ||
30 | + let modelC = new Model(); | ||
31 | + modelC.name = "Raptor"; | ||
32 | + | ||
33 | + let modelD = new Model(); | ||
34 | + modelD.name = "F22"; | ||
35 | + | ||
36 | + let modelE = new Model(); | ||
37 | + modelE.name = "Valkyrie"; | ||
38 | + | ||
39 | + let modelF = new Model(); | ||
40 | + modelF.name = "Battlecruiser"; | ||
41 | + | ||
42 | + console.log("MODEL READY"); | ||
43 | + | ||
44 | + await connection | ||
45 | + .createQueryBuilder() | ||
46 | + .insert() | ||
47 | + .into(Model) | ||
48 | + .values([modelA, modelB, modelC, modelD, modelE, modelF]) | ||
49 | + .execute(); | ||
50 | + | ||
51 | + console.log("MODEL DONE"); | ||
52 | + | ||
53 | + | ||
54 | + let OrgA = new Org(); | ||
55 | + OrgA.name = "서울"; | ||
56 | + | ||
57 | + let OrgB = new Org(); | ||
58 | + OrgB.name = "수원"; | ||
59 | + | ||
60 | + let OrgC = new Org(); | ||
61 | + OrgC.name = "부산"; | ||
62 | + | ||
63 | + let OrgD = new Org(); | ||
64 | + OrgD.name = "춘천"; | ||
65 | + | ||
66 | + console.log("ORG READY"); | ||
67 | + | ||
68 | + await connection | ||
69 | + .createQueryBuilder() | ||
70 | + .insert() | ||
71 | + .into(Org) | ||
72 | + .values([OrgA, OrgB, OrgC, OrgD]) | ||
73 | + .execute(); | ||
74 | + | ||
75 | + console.log("ORG DONE"); | ||
76 | + | ||
77 | + console.log("FAKE OVER"); | ||
78 | + } catch (e) { | ||
79 | + console.log(e, "FAKE ERROR"); | ||
80 | + } | ||
81 | +} |
server/src/global.d.ts
0 → 100644
File mode changed
server/src/index.ts
0 → 100644
server/src/routers/graphiql/index.ts
0 → 100644
1 | +import * as R from "ramda"; | ||
2 | +import { Context } from "../"; | ||
3 | +import Contract from "entity/Contract"; | ||
4 | +import Drone from "entity/Drone"; | ||
5 | +import Dataset from "entity/Dataset"; | ||
6 | +import User from "entity/User"; | ||
7 | +import Org from "entity/Org"; | ||
8 | + | ||
9 | +export default async (root: {}, { id }, context: Context) => { | ||
10 | + const { connection, session } = context; | ||
11 | + console.log("AAA"); | ||
12 | + const userRepo = connection.getRepository(User); | ||
13 | + const datasetRepo = connection.getRepository(Dataset); | ||
14 | + const user = await userRepo.findOneById(session.id); | ||
15 | + | ||
16 | + const datasets = await userRepo | ||
17 | + .createQueryBuilder() | ||
18 | + .relation(User, "datasets") | ||
19 | + .of(user) | ||
20 | + .loadMany(); | ||
21 | + | ||
22 | + const dataset = await datasetRepo.findOneById(id); | ||
23 | + console.log(user); | ||
24 | + user!.datasets = [...datasets, dataset]; | ||
25 | + | ||
26 | + try { | ||
27 | + const result = await userRepo.save(user!); | ||
28 | + console.log("UPDATE", result); | ||
29 | + } catch (err) { | ||
30 | + console.log(err, 11111); | ||
31 | + } | ||
32 | + | ||
33 | + return dataset; | ||
34 | +}; |
1 | +import * as R from "ramda"; | ||
2 | +import { Context } from "../"; | ||
3 | +import Contract from "entity/Contract"; | ||
4 | +import Drone from "entity/Drone"; | ||
5 | +import User from "entity/User"; | ||
6 | +import Org from "entity/Org"; | ||
7 | + | ||
8 | +export default async ( | ||
9 | + root: {}, | ||
10 | + { input: { reason, date, droneId, area, address } }, | ||
11 | + context: Context | ||
12 | +) => { | ||
13 | + const { connection } = context; | ||
14 | + const contractRepo = connection.getRepository(Contract); | ||
15 | + const droneRepo = connection.getRepository(Drone); | ||
16 | + const userRepo = connection.getRepository(User); | ||
17 | + const orgRepo = connection.getRepository(Org); | ||
18 | + | ||
19 | + const userId = context.session.id; | ||
20 | + const user = await userRepo.findOneById(userId); | ||
21 | + | ||
22 | + if (R.isNil(user)) throw new Error("Session Error"); | ||
23 | + | ||
24 | + const drone = await droneRepo.findOneById(droneId); | ||
25 | + | ||
26 | + if (R.isNil(drone)) throw new Error("Invalid Drone ID"); | ||
27 | + | ||
28 | + const org = await orgRepo.findOne({ where: { name: area } }); | ||
29 | + | ||
30 | + let contract = new Contract(); | ||
31 | + | ||
32 | + contract.reason = reason; | ||
33 | + contract.date = date; | ||
34 | + contract.area = area; | ||
35 | + contract.address = address; | ||
36 | + contract.drone = drone; | ||
37 | + contract.status = "wait"; | ||
38 | + contract.review = "심사중"; | ||
39 | + contract.writer = user!; | ||
40 | + contract.org = org!; | ||
41 | + | ||
42 | + await contractRepo.save(contract); | ||
43 | + return contract; | ||
44 | +}; |
1 | +import * as R from "ramda"; | ||
2 | +import { Context } from "../"; | ||
3 | +import Contract from "entity/Contract"; | ||
4 | +import Drone from "entity/Drone"; | ||
5 | +import Dataset from "entity/Dataset"; | ||
6 | +import User from "entity/User"; | ||
7 | +import Org from "entity/Org"; | ||
8 | + | ||
9 | +export default async ( | ||
10 | + root: {}, | ||
11 | + { input: { comment, droneId } }, | ||
12 | + context: Context | ||
13 | +) => { | ||
14 | + const { connection } = context; | ||
15 | + const datasetRepo = connection.getRepository(Dataset); | ||
16 | + const droneRepo = connection.getRepository(Drone); | ||
17 | + | ||
18 | + const drone = await droneRepo.findOneById(droneId); | ||
19 | + | ||
20 | + let dataset = new Dataset(); | ||
21 | + | ||
22 | + dataset.comment = comment; | ||
23 | + dataset.producer = drone!; | ||
24 | + | ||
25 | + await datasetRepo.save(dataset); | ||
26 | + return dataset; | ||
27 | +}; |
1 | +import { Context } from "../"; | ||
2 | +import User from "entity/User"; | ||
3 | + | ||
4 | +export default async ( | ||
5 | + root: {}, | ||
6 | + { input: { email, password } }, | ||
7 | + context: Context | ||
8 | +) => { | ||
9 | + const { connection } = context; | ||
10 | + const userRepo = connection.getRepository(User); | ||
11 | + let user = new User(); | ||
12 | + user.email = email; | ||
13 | + user.password = password; | ||
14 | + | ||
15 | + await userRepo.save(user); | ||
16 | + return user; | ||
17 | +}; |
server/src/routers/graphql/Mutation/index.ts
0 → 100644
1 | +import buyDataset from "./buyDataset"; | ||
2 | +import createUser from "./createUser"; | ||
3 | +import createContract from "./createContract"; | ||
4 | +import createDataset from "./createDataset"; | ||
5 | +import login from "./login"; | ||
6 | +import registerDrone from "./registerDrone"; | ||
7 | +import judgeContract from "./judgeContract"; | ||
8 | + | ||
9 | +export default { | ||
10 | + buyDataset, | ||
11 | + createContract, | ||
12 | + createUser, | ||
13 | + createDataset, | ||
14 | + judgeContract, | ||
15 | + login, | ||
16 | + registerDrone | ||
17 | +}; |
1 | +import * as R from "ramda"; | ||
2 | +import { Context } from "../"; | ||
3 | +import Contract from "entity/Contract"; | ||
4 | +import Drone from "entity/Drone"; | ||
5 | +import User from "entity/User"; | ||
6 | +import Org from "entity/Org"; | ||
7 | + | ||
8 | +export default async (root: {}, { id, ok, review }, context: Context) => { | ||
9 | + const { connection } = context; | ||
10 | + const contractRepo = connection.getRepository(Contract); | ||
11 | + | ||
12 | + const result = await contractRepo | ||
13 | + .createQueryBuilder() | ||
14 | + .update(Contract) | ||
15 | + .set({ status: ok ? "ok" : "rejected", review }) | ||
16 | + .where("id = :id", { id }) | ||
17 | + .execute(); | ||
18 | + | ||
19 | + return null; | ||
20 | +}; |
server/src/routers/graphql/Mutation/login.ts
0 → 100644
1 | +import { Context } from "../"; | ||
2 | +import User from "entity/User"; | ||
3 | +import * as R from "ramda"; | ||
4 | + | ||
5 | +// password를 암호화하자 | ||
6 | +export default async ( | ||
7 | + root: {}, | ||
8 | + { input: { email, password } }, | ||
9 | + context: Context | ||
10 | +) => { | ||
11 | + const { connection } = context; | ||
12 | + const userRepo = connection.getRepository(User); | ||
13 | + | ||
14 | + const user = await userRepo.findOne({ | ||
15 | + where: { | ||
16 | + email, | ||
17 | + password | ||
18 | + } | ||
19 | + }); | ||
20 | + | ||
21 | + if (R.isNil(user)) return null; | ||
22 | + | ||
23 | + const token = user!.id; | ||
24 | + return { user, token }; | ||
25 | +}; |
1 | +import * as R from "ramda"; | ||
2 | +import { Context } from "../"; | ||
3 | +import Drone from "entity/Drone"; | ||
4 | +import Model from "entity/Model"; | ||
5 | +import User from "entity/User"; | ||
6 | + | ||
7 | +export default async (root: {}, { input: { modelId, name } }, context: Context) => { | ||
8 | + const { connection, session } = context; | ||
9 | + const droneRepo = connection.getRepository(Drone); | ||
10 | + const modelRepo = connection.getRepository(Model); | ||
11 | + const userRepo = connection.getRepository(User); | ||
12 | + | ||
13 | + const userId = session.id; | ||
14 | + const user = await userRepo.findOneById(userId); | ||
15 | + | ||
16 | + if (R.isNil(user)) throw new Error("Session Error"); | ||
17 | + | ||
18 | + const model = await modelRepo.findOneById(modelId); | ||
19 | + | ||
20 | + if (R.isNil(model)) throw new Error("Invalid Drone Model"); | ||
21 | + | ||
22 | + let drone = new Drone(); | ||
23 | + drone.name = name; | ||
24 | + drone.date = new Date().toString(); | ||
25 | + drone.owner = user!; | ||
26 | + drone.model = model; | ||
27 | + | ||
28 | + await droneRepo.save(drone); | ||
29 | + console.log(drone); | ||
30 | + return drone; | ||
31 | +}; |
1 | +import { Context } from "../"; | ||
2 | +import Dataset from "entity/Dataset"; | ||
3 | + | ||
4 | +export default async (root: {}, { id }, context: Context) => { | ||
5 | + const { connection } = context; | ||
6 | + const datasetRepo = connection.getRepository(Dataset); | ||
7 | + const datasets = await datasetRepo.find(); | ||
8 | + console.log(datasets); | ||
9 | + return datasets; | ||
10 | +}; |
1 | +import { Context } from "../"; | ||
2 | +import Contract from "entity/Contract"; | ||
3 | + | ||
4 | +export default async (root: {}, { id }, context: Context) => { | ||
5 | + const { connection } = context; | ||
6 | + const contractRepo = connection.getRepository(Contract); | ||
7 | + const contract = await contractRepo.findOneById(id); | ||
8 | + return contract; | ||
9 | +}; |
1 | +import { Context } from "../"; | ||
2 | +import User from "entity/User"; | ||
3 | + | ||
4 | +export default async (root: {}, {}, context: Context) => { | ||
5 | + const { connection, session } = context; | ||
6 | + const userRepo = connection.getRepository(User); | ||
7 | + const userId = session.id; | ||
8 | + const user = await userRepo.findOneById(userId); | ||
9 | + console.log(user); | ||
10 | + return user; | ||
11 | +}; |
server/src/routers/graphql/Query/fetchOrg.ts
0 → 100644
server/src/routers/graphql/Query/index.ts
0 → 100644
1 | +import fetchAllModels from "./fetchAllModels"; | ||
2 | +import fetchAllDatasets from "./fetchAllDatasets"; | ||
3 | +import fetchAllOrgs from "./fetchAllOrgs"; | ||
4 | +import fetchUser from "./fetchUser"; | ||
5 | +import fetchContract from "./fetchContract"; | ||
6 | +import fetchCurrentUser from "./fetchCurrentUser"; | ||
7 | +import fetchOrg from "./fetchOrg"; | ||
8 | + | ||
9 | +export default { | ||
10 | + fetchAllModels, | ||
11 | + fetchAllDatasets, | ||
12 | + fetchAllOrgs, | ||
13 | + fetchUser, | ||
14 | + fetchOrg, | ||
15 | + fetchContract, | ||
16 | + fetchCurrentUser | ||
17 | +}; |
server/src/routers/graphql/Types/Contract.ts
0 → 100644
1 | +import * as R from "ramda"; | ||
2 | +import { Context } from "../"; | ||
3 | +import Contract from "entity/Contract"; | ||
4 | + | ||
5 | +export default { | ||
6 | + async drone(contract, {}, context: Context) { | ||
7 | + if (contract.drone) return contract.drone; | ||
8 | + | ||
9 | + const contractRepo = context.connection.getRepository(Contract); | ||
10 | + | ||
11 | + const model = await contractRepo | ||
12 | + .createQueryBuilder() | ||
13 | + .relation(Contract, "drone") | ||
14 | + .of(contract) | ||
15 | + .loadOne(); | ||
16 | + | ||
17 | + return model; | ||
18 | + } | ||
19 | +}; |
server/src/routers/graphql/Types/Dataset.ts
0 → 100644
1 | +import * as R from "ramda"; | ||
2 | +import { Context } from "../"; | ||
3 | +import Dataset from "entity/Dataset"; | ||
4 | + | ||
5 | +export default { | ||
6 | + async producer(dataset, {}, context: Context) { | ||
7 | + if (dataset.producer) return dataset.producer; | ||
8 | + | ||
9 | + const datasetRepo = context.connection.getRepository(Dataset); | ||
10 | + | ||
11 | + const producer = await datasetRepo | ||
12 | + .createQueryBuilder() | ||
13 | + .relation(Dataset, "producer") | ||
14 | + .of(dataset) | ||
15 | + .loadOne(); | ||
16 | + | ||
17 | + return producer; | ||
18 | + } | ||
19 | +}; |
server/src/routers/graphql/Types/Drone.ts
0 → 100644
1 | +import * as R from "ramda"; | ||
2 | +import { Context } from "../"; | ||
3 | +import Drone from "entity/Drone"; | ||
4 | + | ||
5 | +export default { | ||
6 | + async model(drone, {}, context: Context) { | ||
7 | + if (drone.model) return drone.model; | ||
8 | + | ||
9 | + const droneRepo = context.connection.getRepository(Drone); | ||
10 | + | ||
11 | + const model = await droneRepo | ||
12 | + .createQueryBuilder() | ||
13 | + .relation(Drone, "model") | ||
14 | + .of(drone) | ||
15 | + .loadOne(); | ||
16 | + | ||
17 | + return model; | ||
18 | + }, | ||
19 | + async owner(drone, {}, context: Context) { | ||
20 | + if (drone.owner) return drone.owner; | ||
21 | + | ||
22 | + const droneRepo = context.connection.getRepository(Drone); | ||
23 | + | ||
24 | + const owner = await droneRepo | ||
25 | + .createQueryBuilder() | ||
26 | + .relation(Drone, "owner") | ||
27 | + .of(drone) | ||
28 | + .loadOne(); | ||
29 | + | ||
30 | + return owner; | ||
31 | + } | ||
32 | +}; |
server/src/routers/graphql/Types/Model.ts
0 → 100644
server/src/routers/graphql/Types/Org.ts
0 → 100644
1 | +import * as R from "ramda"; | ||
2 | +import { Context } from "../"; | ||
3 | +import Org from "entity/Org"; | ||
4 | + | ||
5 | +export default { | ||
6 | + async contracts(org, {}, context: Context) { | ||
7 | + if (org.contracts) return org.model; | ||
8 | + | ||
9 | + const orgRepo = context.connection.getRepository(Org); | ||
10 | + | ||
11 | + const contracts = await orgRepo | ||
12 | + .createQueryBuilder() | ||
13 | + .relation(Org, "contracts") | ||
14 | + .of(org) | ||
15 | + .loadOne(); | ||
16 | + | ||
17 | + console.log("Hello", contracts); | ||
18 | + return contracts; | ||
19 | + } | ||
20 | +}; |
server/src/routers/graphql/Types/User.ts
0 → 100644
1 | +import * as R from "ramda"; | ||
2 | +import { Context } from "../"; | ||
3 | +import User from "entity/User"; | ||
4 | + | ||
5 | +export default { | ||
6 | + async drones(user, {}, context: Context) { | ||
7 | + const userRepo = context.connection.getRepository(User); | ||
8 | + | ||
9 | + const drones = await userRepo | ||
10 | + .createQueryBuilder() | ||
11 | + .relation(User, "drones") | ||
12 | + .of(user) | ||
13 | + .loadMany(); | ||
14 | + | ||
15 | + return drones; | ||
16 | + }, | ||
17 | + async contracts(user, {}, context: Context) { | ||
18 | + const userRepo = context.connection.getRepository(User); | ||
19 | + | ||
20 | + const contracts = await userRepo | ||
21 | + .createQueryBuilder() | ||
22 | + .relation(User, "contracts") | ||
23 | + .of(user) | ||
24 | + .loadMany(); | ||
25 | + | ||
26 | + return contracts; | ||
27 | + }, | ||
28 | + async datasets(user, {}, context: Context) { | ||
29 | + const userRepo = context.connection.getRepository(User); | ||
30 | + | ||
31 | + const datasets = await userRepo | ||
32 | + .createQueryBuilder() | ||
33 | + .relation(User, "datasets") | ||
34 | + .of(user) | ||
35 | + .loadMany(); | ||
36 | + | ||
37 | + return datasets; | ||
38 | + } | ||
39 | +}; |
server/src/routers/graphql/Types/index.ts
0 → 100644
server/src/routers/graphql/index.ts
0 → 100644
1 | +import * as express from "express"; | ||
2 | +import { graphqlExpress } from "apollo-server-express"; | ||
3 | +import * as R from "ramda"; | ||
4 | +import * as jwt from "jsonwebtoken"; | ||
5 | +import * as bodyParser from "body-parser"; | ||
6 | +import schema from "./schema"; | ||
7 | +import { Connection } from "typeorm"; | ||
8 | + | ||
9 | +const { apolloUploadExpress } = require("apollo-upload-server"); | ||
10 | + | ||
11 | +const router = express.Router(); | ||
12 | +const formatError = R.always("GraphQL Error"); // OFF NOW | ||
13 | + | ||
14 | +const extractSession = req => | ||
15 | + req.headers["authorization"] | ||
16 | + ? req.headers["authorization"].split(" ")[1] | ||
17 | + : null; | ||
18 | + | ||
19 | +export interface Context { | ||
20 | + connection: Connection; | ||
21 | + session: { | ||
22 | + id: string; | ||
23 | + }; | ||
24 | +} | ||
25 | + | ||
26 | +router.use( | ||
27 | + "/", | ||
28 | + bodyParser.json(), | ||
29 | + apolloUploadExpress(), | ||
30 | + graphqlExpress(async (req, res) => { | ||
31 | + const connection = req!.app.locals.connection; | ||
32 | + const session = { id: extractSession(req) }; | ||
33 | + console.log(session); | ||
34 | + const context: Context = { connection, session }; | ||
35 | + return { schema, context }; | ||
36 | + }) | ||
37 | +); | ||
38 | + | ||
39 | +export default router; |
server/src/routers/graphql/schema.ts
0 → 100644
1 | +import { makeExecutableSchema } from "graphql-tools"; | ||
2 | +import Query from "./Query"; | ||
3 | +import Mutation from "./Mutation"; | ||
4 | +import Types from "./Types"; | ||
5 | + | ||
6 | +const typeDefs = require("./typeDefs.gql"); | ||
7 | +const { GraphQLUpload: Upload } = require("apollo-upload-server"); | ||
8 | + | ||
9 | +const resolvers = { | ||
10 | + Query, | ||
11 | + Mutation, | ||
12 | + ...Types | ||
13 | +}; | ||
14 | + | ||
15 | +export default makeExecutableSchema({ typeDefs, resolvers }); |
server/src/routers/graphql/typeDefs.gql
0 → 100644
1 | +type Contract { | ||
2 | + id: ID! | ||
3 | + reason: String! | ||
4 | + date: String! | ||
5 | + address: String! | ||
6 | + area: String! | ||
7 | + status: String! | ||
8 | + review: String! | ||
9 | + writer: User! | ||
10 | + drone: Drone! | ||
11 | +} | ||
12 | + | ||
13 | +type Drone { | ||
14 | + id: ID! | ||
15 | + name: String! | ||
16 | + date: String! | ||
17 | + model: Model! | ||
18 | + datasets: [Dataset!]! | ||
19 | + owner: User! | ||
20 | +} | ||
21 | + | ||
22 | +type Dataset { | ||
23 | + id: ID! | ||
24 | + comment: String! | ||
25 | + producer: Drone! | ||
26 | +} | ||
27 | + | ||
28 | +type Model { | ||
29 | + id: ID! | ||
30 | + name: String! | ||
31 | +} | ||
32 | + | ||
33 | +type Org { | ||
34 | + id: ID! | ||
35 | + name: String! | ||
36 | + contracts: [Contract!]! | ||
37 | +} | ||
38 | + | ||
39 | +type User { | ||
40 | + id: ID! | ||
41 | + email: String! | ||
42 | + password: String! | ||
43 | + contracts: [Contract!]! | ||
44 | + drones: [Drone!]! | ||
45 | + datasets: [Dataset!]! | ||
46 | +} | ||
47 | + | ||
48 | +type Query { | ||
49 | + fetchAllModels: [Model!]! | ||
50 | + fetchAllDatasets: [Dataset!]! | ||
51 | + fetchAllOrgs: [Org!]! | ||
52 | + fetchCurrentUser: User | ||
53 | + fetchOrg(id: ID!): Org | ||
54 | + fetchUser(id: ID!): User | ||
55 | + fetchContract(id: ID!): Contract | ||
56 | +} | ||
57 | + | ||
58 | +type Mutation { | ||
59 | + buyDataset(id: ID!): Dataset | ||
60 | + createContract(input: ContractInput!): Contract | ||
61 | + createUser(input: UserInput!): User | ||
62 | + createDataset(input: DatasetInput!): Dataset | ||
63 | + login(input: UserInput!): LoginResult | ||
64 | + registerDrone(input: DroneInput!): Drone | ||
65 | + judgeContract(id: ID!, ok: Boolean!, review: String!): Contract | ||
66 | + | ||
67 | +} | ||
68 | + | ||
69 | +input ContractInput { | ||
70 | + droneId: ID! | ||
71 | + reason: String! | ||
72 | + date: String! | ||
73 | + address: String! | ||
74 | + area: String! | ||
75 | +} | ||
76 | + | ||
77 | +input DroneInput { | ||
78 | + modelId: ID! | ||
79 | + name: String! | ||
80 | +} | ||
81 | + | ||
82 | +input DatasetInput { | ||
83 | + droneId: ID! | ||
84 | + comment: String! | ||
85 | +} | ||
86 | + | ||
87 | +type LoginResult { | ||
88 | + user: User! | ||
89 | + token: String! | ||
90 | +} | ||
91 | + | ||
92 | +input UserInput { | ||
93 | + email: String! | ||
94 | + password: String! | ||
95 | +} | ||
96 | + | ||
97 | +type Schema { | ||
98 | + query: Query | ||
99 | + mutation: Mutation | ||
100 | +} |
server/tsconfig.json
0 → 100644
1 | +{ | ||
2 | + "compilerOptions": { | ||
3 | + "outDir": "../build/server", | ||
4 | + "module": "es2015", | ||
5 | + "target": "es5", | ||
6 | + "lib": [ | ||
7 | + "es6", | ||
8 | + "dom" | ||
9 | + ], | ||
10 | + "sourceMap": true, | ||
11 | + "allowJs": true, | ||
12 | + "moduleResolution": "node", | ||
13 | + "forceConsistentCasingInFileNames": true, | ||
14 | + "noImplicitReturns": true, | ||
15 | + "noImplicitThis": true, | ||
16 | + "emitDecoratorMetadata": true, | ||
17 | + "experimentalDecorators": true, | ||
18 | + "noImplicitAny": false, | ||
19 | + "strictNullChecks": true, | ||
20 | + "suppressImplicitAnyIndexErrors": true, | ||
21 | + "noUnusedLocals": false, | ||
22 | + "baseUrl": ".", | ||
23 | + "paths": { | ||
24 | + "*": [ | ||
25 | + "node_modules/*", | ||
26 | + "src/*", | ||
27 | + "lib/*" | ||
28 | + ] | ||
29 | + } | ||
30 | + }, | ||
31 | + "exclude": [ | ||
32 | + "node_modules", | ||
33 | + "tmp" | ||
34 | + ] | ||
35 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
server/tslint.json
0 → 100644
1 | +{ | ||
2 | + "rules": { | ||
3 | + "align": [ | ||
4 | + false, | ||
5 | + "parameters", | ||
6 | + "arguments", | ||
7 | + "statements" | ||
8 | + ], | ||
9 | + "ban": false, | ||
10 | + "class-name": false, | ||
11 | + "comment-format": [ | ||
12 | + false, | ||
13 | + "check-space" | ||
14 | + ], | ||
15 | + "curly": false, | ||
16 | + "eofline": false, | ||
17 | + "forin": false, | ||
18 | + "indent": [ false, "spaces" ], | ||
19 | + "interface-name": [false, "never-prefix"], | ||
20 | + "jsdoc-format": false, | ||
21 | + "jsx-no-lambda": false, | ||
22 | + "jsx-no-multiline-js": false, | ||
23 | + "label-position": false, | ||
24 | + "max-line-length": [ false, 120 ], | ||
25 | + "member-ordering": [ | ||
26 | + false, | ||
27 | + "public-before-private", | ||
28 | + "static-before-instance", | ||
29 | + "variables-before-functions" | ||
30 | + ], | ||
31 | + "no-any": false, | ||
32 | + "no-arg": false, | ||
33 | + "no-bitwise": false, | ||
34 | + "no-console": [ | ||
35 | + false, | ||
36 | + "log", | ||
37 | + "error", | ||
38 | + "debug", | ||
39 | + "info", | ||
40 | + "time", | ||
41 | + "timeEnd", | ||
42 | + "trace" | ||
43 | + ], | ||
44 | + "no-consecutive-blank-lines": false, | ||
45 | + "no-construct": false, | ||
46 | + "no-debugger": false, | ||
47 | + "no-duplicate-variable": false, | ||
48 | + "no-empty": false, | ||
49 | + "no-eval": false, | ||
50 | + "no-shadowed-variable": false, | ||
51 | + "no-string-literal": false, | ||
52 | + "no-switch-case-fall-through": false, | ||
53 | + "no-trailing-whitespace": false, | ||
54 | + "no-unused-expression": false, | ||
55 | + "no-use-before-declare": false, | ||
56 | + "one-line": [ | ||
57 | + false, | ||
58 | + "check-catch", | ||
59 | + "check-else", | ||
60 | + "check-open-brace", | ||
61 | + "check-whitespace" | ||
62 | + ], | ||
63 | + "quotemark": [false, "single", "jsx-double"], | ||
64 | + "radix": false, | ||
65 | + "semicolon": [false, "always"], | ||
66 | + "switch-default": false, | ||
67 | + | ||
68 | + "trailing-comma": [false], | ||
69 | + | ||
70 | + "triple-equals": [ false, "allow-null-check" ], | ||
71 | + "typedef": [ | ||
72 | + false, | ||
73 | + "parameter", | ||
74 | + "property-declaration" | ||
75 | + ], | ||
76 | + "typedef-whitespace": [ | ||
77 | + false, | ||
78 | + { | ||
79 | + "call-signature": "nospace", | ||
80 | + "index-signature": "nospace", | ||
81 | + "parameter": "nospace", | ||
82 | + "property-declaration": "nospace", | ||
83 | + "variable-declaration": "nospace" | ||
84 | + } | ||
85 | + ], | ||
86 | + "variable-name": [false, "ban-keywords", "check-format", "allow-leading-underscore", "allow-pascal-case"], | ||
87 | + "whitespace": [ | ||
88 | + false, | ||
89 | + "check-branch", | ||
90 | + "check-decl", | ||
91 | + "check-module", | ||
92 | + "check-operator", | ||
93 | + "check-separator", | ||
94 | + "check-type", | ||
95 | + "check-typecast" | ||
96 | + ] | ||
97 | + } | ||
98 | +} |
-
Please register or login to post a comment