Showing
61 changed files
with
2644 additions
and
0 deletions
code/.idea/.gitignore
0 → 100644
code/.idea/code.iml
0 → 100644
1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
2 | +<module type="WEB_MODULE" version="4"> | ||
3 | + <component name="NewModuleRootManager"> | ||
4 | + <content url="file://$MODULE_DIR$" /> | ||
5 | + <orderEntry type="inheritedJdk" /> | ||
6 | + <orderEntry type="sourceFolder" forTests="false" /> | ||
7 | + </component> | ||
8 | +</module> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
code/.idea/misc.xml
0 → 100644
code/.idea/modules.xml
0 → 100644
1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
2 | +<project version="4"> | ||
3 | + <component name="ProjectModuleManager"> | ||
4 | + <modules> | ||
5 | + <module fileurl="file://$PROJECT_DIR$/.idea/code.iml" filepath="$PROJECT_DIR$/.idea/code.iml" /> | ||
6 | + </modules> | ||
7 | + </component> | ||
8 | +</project> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
code/.idea/vcs.xml
0 → 100644
1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
2 | +<project version="4"> | ||
3 | + <component name="VcsDirectoryMappings"> | ||
4 | + <mapping directory="$PROJECT_DIR$/.." vcs="Git" /> | ||
5 | + <mapping directory="$PROJECT_DIR$/locationTest/expo-location-example" vcs="Git" /> | ||
6 | + <mapping directory="$PROJECT_DIR$/my-project" vcs="Git" /> | ||
7 | + <mapping directory="$PROJECT_DIR$/render_server_react_native" vcs="Git" /> | ||
8 | + <mapping directory="$PROJECT_DIR$/render_server_react_native/@expo/vector-icons" vcs="Git" /> | ||
9 | + </component> | ||
10 | +</project> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
code/render_server_react_native/.gitignore
0 → 100644
code/render_server_react_native/App.js
0 → 100644
1 | +import React, {useState} from 'react'; | ||
2 | +import {StyleSheet, Text, View, Image, StatusBar, AsyncStorage} from 'react-native'; | ||
3 | +import {AppLoading} from "expo"; | ||
4 | +import {Asset} from 'expo-asset'; | ||
5 | +import {Provider} from 'react-redux'; | ||
6 | +import * as Font from 'expo-font' | ||
7 | +import {Ionicons} from "@expo/vector-icons"; | ||
8 | +import {NavigationContainer} from "@react-navigation/native"; | ||
9 | +import store from './store'; | ||
10 | +import StackNavigation from "./navigations/StackNavigation"; | ||
11 | +import {AuthProvider} from "./AuthContext"; | ||
12 | +import host from './env'; | ||
13 | +import axios from "axios"; | ||
14 | + | ||
15 | +const cacheImages = (images) => { | ||
16 | + return images.map((image) => { | ||
17 | + if (typeof image === 'string') { | ||
18 | + return Image.prefetch(image); | ||
19 | + } else { | ||
20 | + return Asset.fromModule(image).downloadAsync(); | ||
21 | + } | ||
22 | + }) | ||
23 | +}; | ||
24 | + | ||
25 | +const cacheFonts = (fonts) => { | ||
26 | + return fonts.map((font) => { | ||
27 | + return Font.loadAsync(font); | ||
28 | + }) | ||
29 | +}; | ||
30 | + | ||
31 | +const App = () => { | ||
32 | + const [isReady, setIsReady] = useState(false); | ||
33 | + const [user, setUser] = useState(''); | ||
34 | + | ||
35 | + const loadAssets = async () => { | ||
36 | + const images = cacheImages( | ||
37 | + ['https://images.unsplash.com/photo-1532278951723-545f655c97f9?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=60'] | ||
38 | + ); | ||
39 | + const fonts = cacheFonts([Ionicons.font]); | ||
40 | + | ||
41 | + const cookie = await AsyncStorage.getItem('cookie'); | ||
42 | + console.log('cookie', cookie); | ||
43 | + if (cookie) { | ||
44 | + try { | ||
45 | + axios.defaults.headers.Cookie = cookie; | ||
46 | + console.log('user/loadMe 요청보냄', `http://${host}:4001/user/loadMe`); | ||
47 | + const res = await axios.get(`http://${host}:4001/user/loadMe`); | ||
48 | + const {user} = res.data; | ||
49 | + console.log(user); | ||
50 | + setUser(user); | ||
51 | + } catch (e) { | ||
52 | + console.error(e); | ||
53 | + } | ||
54 | + } | ||
55 | + | ||
56 | + return Promise.all([images, fonts]); | ||
57 | + }; | ||
58 | + | ||
59 | + const onFinish = () => { | ||
60 | + setIsReady(true); | ||
61 | + }; | ||
62 | + const onError = (err) => { | ||
63 | + console.error(err) | ||
64 | + }; | ||
65 | + return ( | ||
66 | + <> | ||
67 | + {isReady | ||
68 | + ? | ||
69 | + // <Provider store={store}> | ||
70 | + // <NavigationContainer> | ||
71 | + // <StatusBar barstyle={'light-content'}/> | ||
72 | + // <StackNavigation/> | ||
73 | + // </NavigationContainer> | ||
74 | + // </Provider> | ||
75 | + <Provider store={store}> | ||
76 | + <AuthProvider user={user}> | ||
77 | + <NavigationContainer> | ||
78 | + <StatusBar barstyle={'light-content'}/> | ||
79 | + <StackNavigation/> | ||
80 | + </NavigationContainer> | ||
81 | + </AuthProvider> | ||
82 | + </Provider> | ||
83 | + : | ||
84 | + <AppLoading | ||
85 | + startAsync={loadAssets} | ||
86 | + onFinish={onFinish} | ||
87 | + onError={onError} | ||
88 | + /> | ||
89 | + } | ||
90 | + </> | ||
91 | + ); | ||
92 | +}; | ||
93 | + | ||
94 | + | ||
95 | +export default App; | ||
96 | + | ||
97 | +const styles = StyleSheet.create({ | ||
98 | + container: { | ||
99 | + flex: 1, | ||
100 | + backgroundColor: '#fff', | ||
101 | + alignItems: 'center', | ||
102 | + justifyContent: 'center', | ||
103 | + }, | ||
104 | +}); |
1 | +import React, {createContext, useContext, useEffect, useState} from 'react'; | ||
2 | +import {useDispatch, useSelector} from 'react-redux'; | ||
3 | +import {LOAD_ME_SUCCESS} from "./reducers/user"; | ||
4 | + | ||
5 | +export const AuthContext = createContext({}); | ||
6 | +export const AuthProvider = (props) => { | ||
7 | + const {children, user} = props; | ||
8 | + | ||
9 | + const dispatch = useDispatch(); | ||
10 | + | ||
11 | + const onLoadMe = async () => { | ||
12 | + try { | ||
13 | + await dispatch({ | ||
14 | + type: LOAD_ME_SUCCESS, | ||
15 | + data: { | ||
16 | + user | ||
17 | + } | ||
18 | + }); | ||
19 | + } catch (e) { | ||
20 | + console.log(e); | ||
21 | + } | ||
22 | + }; | ||
23 | + | ||
24 | + useEffect(() => { | ||
25 | + onLoadMe(); | ||
26 | + console.log('AuthContext user', user); | ||
27 | + }, [user]); | ||
28 | + | ||
29 | + return ( | ||
30 | + <AuthContext.Provider> | ||
31 | + {children} | ||
32 | + </AuthContext.Provider> | ||
33 | + ) | ||
34 | +}; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
code/render_server_react_native/app.json
0 → 100644
1 | +{ | ||
2 | + "expo": { | ||
3 | + "name": "render_server_react_native", | ||
4 | + "slug": "render_server_react_native", | ||
5 | + "platforms": [ | ||
6 | + "ios", | ||
7 | + "android", | ||
8 | + "web" | ||
9 | + ], | ||
10 | + "version": "1.0.0", | ||
11 | + "orientation": "portrait", | ||
12 | + "icon": "./assets/icon.png", | ||
13 | + "splash": { | ||
14 | + "image": "./assets/splash.png", | ||
15 | + "resizeMode": "contain", | ||
16 | + "backgroundColor": "#ffffff" | ||
17 | + }, | ||
18 | + "updates": { | ||
19 | + "fallbackToCacheTimeout": 0 | ||
20 | + }, | ||
21 | + "assetBundlePatterns": [ | ||
22 | + "**/*" | ||
23 | + ], | ||
24 | + "ios": { | ||
25 | + "supportsTablet": true | ||
26 | + } | ||
27 | + } | ||
28 | +} |

642 Bytes

9.09 KB
1 | +import React from 'react'; | ||
2 | +import {ActivityIndicator, View} from 'react-native'; | ||
3 | + | ||
4 | +const LoadingComponent = () => { | ||
5 | + return ( | ||
6 | + <View style={{ | ||
7 | + flex: 1, | ||
8 | + justifyContent: 'center', | ||
9 | + alignItems: 'center' | ||
10 | + }}> | ||
11 | + <ActivityIndicator color={'grey'}/> | ||
12 | + </View> | ||
13 | + ) | ||
14 | +}; | ||
15 | + | ||
16 | +export default LoadingComponent; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +import React, {useState, useContext, useEffect, useCallback} from 'react'; | ||
2 | +import {View, Text, Button, StyleSheet, TextInput, TouchableOpacity} from 'react-native'; | ||
3 | +import {useDispatch, useSelector} from "react-redux"; | ||
4 | +import {LOG_IN_REQUEST, LOG_OUT_REQUEST} from "../reducers/user"; | ||
5 | +import {MaterialCommunityIcons} from "@expo/vector-icons"; | ||
6 | +import styled from "styled-components"; | ||
7 | +import {useNavigation} from '@react-navigation/native'; | ||
8 | +import LoadingComponent from "../components/LoadingComponent"; | ||
9 | +import SignUpComponent from "./SignUpComponent"; | ||
10 | + | ||
11 | + | ||
12 | +const LoginButton = styled.TouchableOpacity` | ||
13 | + align-items: center; | ||
14 | + justify-content: center; | ||
15 | + width: 60px; | ||
16 | + height: 40px; | ||
17 | + background-color: #e6e6fa; | ||
18 | + border: 1px; | ||
19 | +`; | ||
20 | + | ||
21 | + | ||
22 | +const LoginComponent = () => { | ||
23 | + const navigation = useNavigation(); | ||
24 | + const [loading, setLoading] = useState(true); | ||
25 | + const [email, setEmail] = useState(''); | ||
26 | + const [password, setPassword] = useState(''); | ||
27 | + | ||
28 | + | ||
29 | + const {me} = useSelector(state => state.user); | ||
30 | + const {isLoggingIn} = useSelector(state => state.user); | ||
31 | + | ||
32 | + const onChangeEmail = (email) => { | ||
33 | + setEmail(email) | ||
34 | + }; | ||
35 | + | ||
36 | + const onChangePassword = (password) => { | ||
37 | + setPassword(password); | ||
38 | + }; | ||
39 | + | ||
40 | + const dispatch = useDispatch(); | ||
41 | + const onSubmit = async () => { | ||
42 | + if (!email || !password) { | ||
43 | + return | ||
44 | + } | ||
45 | + await dispatch({ | ||
46 | + type: LOG_IN_REQUEST, | ||
47 | + data: { | ||
48 | + email, | ||
49 | + password | ||
50 | + } | ||
51 | + }); | ||
52 | + }; | ||
53 | + | ||
54 | + | ||
55 | + useEffect(() => { | ||
56 | + setLoading(false); | ||
57 | + setEmail(''); | ||
58 | + setPassword(''); | ||
59 | + }, []); | ||
60 | + | ||
61 | + return ( | ||
62 | + <View style={styles.containerStyle}> | ||
63 | + <TextInput | ||
64 | + style={styles.input} | ||
65 | + placeholder="Type here to Email!" | ||
66 | + onChangeText={onChangeEmail} | ||
67 | + defaultValue={email} | ||
68 | + /> | ||
69 | + <TextInput | ||
70 | + style={styles.input} | ||
71 | + placeholder="Type here to password!" | ||
72 | + type="password" | ||
73 | + onChangeText={onChangePassword} | ||
74 | + /> | ||
75 | + <LoginButton | ||
76 | + title={'Login'} | ||
77 | + onPress={onSubmit}> | ||
78 | + <Text style={{color: '#696969'}}>Login</Text> | ||
79 | + </LoginButton> | ||
80 | + </View> | ||
81 | + ) | ||
82 | +}; | ||
83 | + | ||
84 | +const styles = StyleSheet.create({ | ||
85 | + containerStyle: { | ||
86 | + flex: 1, | ||
87 | + alignItems: 'center', | ||
88 | + justifyContent: 'center', | ||
89 | + backgroundColor: '#ecf0f1', | ||
90 | + marginTop: 100, | ||
91 | + }, | ||
92 | + input: { | ||
93 | + width: 200, | ||
94 | + height: 44, | ||
95 | + padding: 10, | ||
96 | + borderWidth: 1, | ||
97 | + borderColor: '#778899', | ||
98 | + marginBottom: 10, | ||
99 | + } | ||
100 | +}); | ||
101 | + | ||
102 | +export default LoginComponent; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +import React, {useState, useContext, useEffect, useCallback} from 'react'; | ||
2 | +import {View, Text, Button, TextInput, TouchableOpacity, StyleSheet} from 'react-native'; | ||
3 | +import {useDispatch, useSelector} from "react-redux"; | ||
4 | +import {LOG_IN_REQUEST, LOG_OUT_REQUEST} from "../reducers/user"; | ||
5 | +import {MaterialCommunityIcons} from "@expo/vector-icons"; | ||
6 | +import {useNavigation} from '@react-navigation/native'; | ||
7 | +import LoadingComponent from "../components/LoadingComponent"; | ||
8 | + | ||
9 | +const MyProfileComponent = () => { | ||
10 | + const navigation = useNavigation(); | ||
11 | + const [loading, setLoading] = useState(true); | ||
12 | + | ||
13 | + const {me} = useSelector(state => state.user); | ||
14 | + const {isLoggingIn} = useSelector(state => state.user); | ||
15 | + | ||
16 | + const dispatch = useDispatch(); | ||
17 | + const onLogout = async () => { | ||
18 | + await dispatch({ | ||
19 | + type: LOG_OUT_REQUEST | ||
20 | + }); | ||
21 | + console.log('onLogout'); | ||
22 | + }; | ||
23 | + return ( | ||
24 | + <View> | ||
25 | + <View style={styles.containerStyle}> | ||
26 | + <Text style={styles.TextStyle}>마이페이지</Text> | ||
27 | + <Text style={styles.TextStyle}>{me.email}</Text> | ||
28 | + <Text style={styles.TextStyle}>{me.nickName}</Text> | ||
29 | + <TouchableOpacity onPress={onLogout}> | ||
30 | + <MaterialCommunityIcons color={'green'} name={'logout'} size={30}/> | ||
31 | + </TouchableOpacity> | ||
32 | + </View> | ||
33 | + </View> | ||
34 | + ) | ||
35 | +}; | ||
36 | + | ||
37 | +const styles = StyleSheet.create({ | ||
38 | + containerStyle: { | ||
39 | + marginTop: 10, | ||
40 | + alignItems: 'center', | ||
41 | + justifyContent: 'center', | ||
42 | + }, | ||
43 | + TextStyle: { | ||
44 | + width: 200, | ||
45 | + height: 44, | ||
46 | + padding: 10, | ||
47 | + borderWidth: 1, | ||
48 | + borderColor: '#778899', | ||
49 | + marginBottom: 10, | ||
50 | + } | ||
51 | +}); | ||
52 | + | ||
53 | +export default MyProfileComponent; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +import React, {useState, useContext, useEffect, useCallback} from 'react'; | ||
2 | +import {View, Text, Button, TextInput, TouchableOpacity} from 'react-native'; | ||
3 | +import {useDispatch, useSelector} from "react-redux"; | ||
4 | +import {SIGN_UP_REQUEST} from "../reducers/user"; | ||
5 | +import {MaterialCommunityIcons} from "@expo/vector-icons"; | ||
6 | +import styled from "styled-components"; | ||
7 | +import {useNavigation} from '@react-navigation/native'; | ||
8 | +import LoadingComponent from "../components/LoadingComponent"; | ||
9 | + | ||
10 | + | ||
11 | +const SignUpButton = styled.TouchableOpacity` | ||
12 | + align-items: center; | ||
13 | + justify-content: center; | ||
14 | + width: 60px; | ||
15 | + height: 60px; | ||
16 | + background-color: #ffffff; | ||
17 | + border: 1px; | ||
18 | +`; | ||
19 | + | ||
20 | +const SignUpComponent = () => { | ||
21 | + const navigation = useNavigation(); | ||
22 | + const [email, setEmail] = useState(''); | ||
23 | + const [nickName, setNickName] = useState(''); | ||
24 | + const [password, setPassword] = useState(''); | ||
25 | + | ||
26 | + | ||
27 | + const {me} = useSelector(state => state.user); | ||
28 | + const {isSigningUp} = useSelector(state => state.user); | ||
29 | + | ||
30 | + const onChangeEmail = (email) => { | ||
31 | + setEmail(email) | ||
32 | + }; | ||
33 | + | ||
34 | + const onChangePassword = (password) => { | ||
35 | + setPassword(password); | ||
36 | + }; | ||
37 | + | ||
38 | + const onChangeNickName = (nickName) => { | ||
39 | + setNickName(nickName) | ||
40 | + }; | ||
41 | + | ||
42 | + const dispatch = useDispatch(); | ||
43 | + const onSubmit = async () => { | ||
44 | + await dispatch({ | ||
45 | + type: SIGN_UP_REQUEST, | ||
46 | + data: { | ||
47 | + email, | ||
48 | + nickName, | ||
49 | + password | ||
50 | + } | ||
51 | + }); | ||
52 | + }; | ||
53 | + | ||
54 | + return ( | ||
55 | + <> | ||
56 | + <View> | ||
57 | + <View> | ||
58 | + <TextInput | ||
59 | + style={{height: 40, marginLeft: 10}} | ||
60 | + placeholder="Type here to Email!" | ||
61 | + onChangeText={onChangeEmail} | ||
62 | + /> | ||
63 | + </View> | ||
64 | + <View> | ||
65 | + <TextInput | ||
66 | + style={{height: 40, marginLeft: 10}} | ||
67 | + placeholder="Type here to nickname!" | ||
68 | + onChangeText={onChangeNickName} | ||
69 | + /> | ||
70 | + </View> | ||
71 | + <View> | ||
72 | + <TextInput | ||
73 | + style={{height: 40, marginLeft: 10}} | ||
74 | + placeholder="Type here to password!" | ||
75 | + type="password" | ||
76 | + onChangeText={onChangePassword} | ||
77 | + /> | ||
78 | + </View> | ||
79 | + <SignUpButton title={'회원가입'} onPress={onSubmit}> | ||
80 | + <Text>회원가입</Text> | ||
81 | + </SignUpButton> | ||
82 | + </View> | ||
83 | + </> | ||
84 | + ) | ||
85 | +}; | ||
86 | + | ||
87 | +export default SignUpComponent; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
code/render_server_react_native/env.js
0 → 100644
1 | +import React, {userLayoutEffect} from 'react'; | ||
2 | +import {createStackNavigator} from "@react-navigation/stack"; | ||
3 | +import TabNavigation from "./TabNavigation"; | ||
4 | +import SelectOrTakePhotoTabNavigation from "./SelectOrTakePhotoTabNavigation"; | ||
5 | +import UploadPhoto from "../screens/UploadPhoto"; | ||
6 | + | ||
7 | +const Stack = createStackNavigator(); | ||
8 | + | ||
9 | +const SelectOrTakePhotoStackNavigation = () =>{ | ||
10 | + return ( | ||
11 | + <Stack.Navigator | ||
12 | + mode='card' | ||
13 | + | ||
14 | + > | ||
15 | + <Stack.Screen | ||
16 | + name='SelectOrTakePhotoTabNavigation' | ||
17 | + component={SelectOrTakePhotoTabNavigation} | ||
18 | + /> | ||
19 | + <Stack.Screen | ||
20 | + name='UploadPhoto' | ||
21 | + component={UploadPhoto} | ||
22 | + /> | ||
23 | + </Stack.Navigator> | ||
24 | + ) | ||
25 | +}; | ||
26 | + | ||
27 | +export default SelectOrTakePhotoStackNavigation; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +import React, {useLayoutEffect} from 'react'; | ||
2 | +import {Ionicons} from "@expo/vector-icons"; | ||
3 | +import {createBottomTabNavigator} from '@react-navigation/bottom-tabs'; | ||
4 | +import TakePhoto from "../screens/TakePhoto"; | ||
5 | +import SelectPhoto from "../screens/SelectPhoto"; | ||
6 | + | ||
7 | +const Tab = createBottomTabNavigator(); | ||
8 | + | ||
9 | +const SelectOrTakePhotoTabNavigation = (props) => { | ||
10 | + const {navigation, route} = props; | ||
11 | + // useLayoutEffect(() => {}, [route]); | ||
12 | + | ||
13 | + return ( | ||
14 | + <Tab.Navigator | ||
15 | + tabBarOptions = {{}} | ||
16 | + > | ||
17 | + <Tab.Screen | ||
18 | + name='SelectPhoto' | ||
19 | + component={SelectPhoto} | ||
20 | + /> | ||
21 | + | ||
22 | + <Tab.Screen | ||
23 | + name='TakePhoto' | ||
24 | + component={TakePhoto} | ||
25 | + /> | ||
26 | + | ||
27 | + </Tab.Navigator> | ||
28 | + ) | ||
29 | +}; | ||
30 | + | ||
31 | +export default SelectOrTakePhotoTabNavigation; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +import React from 'react'; | ||
2 | +import {createStackNavigator} from "@react-navigation/stack"; | ||
3 | +import Gallery from "../screens/Gallery"; | ||
4 | +import Main from "../screens/Main"; | ||
5 | +import TabNavigation from "./TabNavigation"; | ||
6 | +import {TouchableOpacity} from "react-native"; | ||
7 | +// import * as WebBrowser from 'expo-web-browser'; | ||
8 | +import {Ionicons} from "@expo/vector-icons"; | ||
9 | +import SelectOrTakePhotoStackNavigation from "./SelectOrTakePhotoStackNavigation"; | ||
10 | + | ||
11 | + | ||
12 | +const Stack = createStackNavigator(); | ||
13 | +// | ||
14 | +// const openBrowser = (url) => async () => { | ||
15 | +// await WebBrowser.openBrowserAsync(url); | ||
16 | +// }; | ||
17 | + | ||
18 | +const StackNavigation = () =>{ | ||
19 | + return ( | ||
20 | + <Stack.Navigator | ||
21 | + mode='card' | ||
22 | + screenOptions = {{ | ||
23 | + headerRight: () => { | ||
24 | + return ( | ||
25 | + <TouchableOpacity> | ||
26 | + <Ionicons name={'logo-youtube'} color={'red'} size={25}/> | ||
27 | + </TouchableOpacity> | ||
28 | + ) | ||
29 | + } | ||
30 | + }} | ||
31 | + > | ||
32 | + <Stack.Screen | ||
33 | + name='TabNavigation' | ||
34 | + component={TabNavigation} | ||
35 | + /> | ||
36 | + <Stack.Screen | ||
37 | + name='SelectOrTakePhotoStackNavigation' | ||
38 | + component={SelectOrTakePhotoStackNavigation} | ||
39 | + /> | ||
40 | + <Stack.Screen | ||
41 | + name='Gallery' | ||
42 | + component={Gallery} | ||
43 | + /> | ||
44 | + </Stack.Navigator> | ||
45 | + ) | ||
46 | +}; | ||
47 | + | ||
48 | +export default StackNavigation; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +import React, {useLayoutEffect} from 'react'; | ||
2 | +import {Ionicons} from "@expo/vector-icons"; | ||
3 | +import {createBottomTabNavigator} from '@react-navigation/bottom-tabs'; | ||
4 | +import Main from "../screens/Main"; | ||
5 | +import Login from "../screens/Login"; | ||
6 | +import Profile from "../screens/Profile"; | ||
7 | +import Maps from "../screens/Maps"; | ||
8 | + | ||
9 | + | ||
10 | +const Tab = createBottomTabNavigator(); | ||
11 | + | ||
12 | +const getHeaderName = (route) => { | ||
13 | +}; | ||
14 | + | ||
15 | +const TabNavigation = (props) => { | ||
16 | + const {navigation, route} = props; | ||
17 | + // useLayoutEffect(() => {}, [route]); | ||
18 | + | ||
19 | + return ( | ||
20 | + <Tab.Navigator | ||
21 | + // screenOptions = {({route})=>{}} | ||
22 | + tabBarOptions = {{}} | ||
23 | + > | ||
24 | + <Tab.Screen | ||
25 | + name='main' | ||
26 | + component={Main} | ||
27 | + /> | ||
28 | + | ||
29 | + <Tab.Screen | ||
30 | + name='login' | ||
31 | + component={Login} | ||
32 | + /> | ||
33 | + | ||
34 | + <Tab.Screen | ||
35 | + name='maps' | ||
36 | + component={Maps} | ||
37 | + /> | ||
38 | + | ||
39 | + <Tab.Screen | ||
40 | + name='Profile' | ||
41 | + component={Profile} | ||
42 | + /> | ||
43 | + | ||
44 | + </Tab.Navigator> | ||
45 | + ) | ||
46 | +}; | ||
47 | + | ||
48 | +export default TabNavigation; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
code/render_server_react_native/package.json
0 → 100644
1 | +{ | ||
2 | + "main": "node_modules/expo/AppEntry.js", | ||
3 | + "scripts": { | ||
4 | + "start": "expo start", | ||
5 | + "android": "expo start --android", | ||
6 | + "ios": "expo start --ios", | ||
7 | + "web": "expo start --web", | ||
8 | + "eject": "expo eject" | ||
9 | + }, | ||
10 | + "dependencies": { | ||
11 | + "@react-native-community/masked-view": "0.1.6", | ||
12 | + "@react-navigation/bottom-tabs": "^5.4.1", | ||
13 | + "@react-navigation/native": "^5.3.0", | ||
14 | + "@react-navigation/stack": "^5.2.11", | ||
15 | + "axios": "^0.19.2", | ||
16 | + "expo": "~37.0.3", | ||
17 | + "expo-asset": "^8.1.4", | ||
18 | + "expo-camera": "~8.2.0", | ||
19 | + "expo-font": "^8.1.1", | ||
20 | + "expo-media-library": "~8.1.0", | ||
21 | + "expo-permissions": "~8.1.0", | ||
22 | + "expo-web-browser": "^8.2.1", | ||
23 | + "react": "~16.9.0", | ||
24 | + "react-dom": "~16.9.0", | ||
25 | + "react-native": "https://github.com/expo/react-native/archive/sdk-37.0.1.tar.gz", | ||
26 | + "react-native-gesture-handler": "~1.6.0", | ||
27 | + "react-native-maps": "0.26.1", | ||
28 | + "react-native-reanimated": "~1.7.0", | ||
29 | + "react-native-safe-area-context": "0.7.3", | ||
30 | + "react-native-screens": "~2.2.0", | ||
31 | + "react-native-web": "~0.11.7", | ||
32 | + "react-redux": "^7.2.0", | ||
33 | + "redux": "^4.0.5", | ||
34 | + "redux-saga": "^1.1.3", | ||
35 | + "styled-components": "^5.1.0" | ||
36 | + }, | ||
37 | + "devDependencies": { | ||
38 | + "@babel/core": "^7.8.6", | ||
39 | + "@expo/vector-icons": "^10.0.6", | ||
40 | + "babel-preset-expo": "~8.1.0" | ||
41 | + }, | ||
42 | + "private": true | ||
43 | +} |
File mode changed
1 | +export const initialState = { | ||
2 | + me: null, | ||
3 | + | ||
4 | + isLoggingIn: false, | ||
5 | + isSigningUp: false, | ||
6 | + isLoadingMe: false, | ||
7 | + isLoggingOut: false, | ||
8 | + | ||
9 | + info: '', | ||
10 | +}; | ||
11 | + | ||
12 | +export const LOG_IN_REQUEST = 'LOG_IN_REQUEST'; | ||
13 | +export const LOG_IN_SUCCESS = 'LOG_IN_SUCCESS'; | ||
14 | +export const LOG_IN_FAILURE = 'LOG_IN_FAILURE'; | ||
15 | + | ||
16 | +export const SIGN_UP_REQUEST = 'SIGN_UP_REQUEST'; | ||
17 | +export const SIGN_UP_SUCCESS = 'SIGN_UP_SUCCESS'; | ||
18 | +export const SIGN_UP_FAILURE = 'SIGN_UP_FAILURE'; | ||
19 | + | ||
20 | +export const LOAD_ME_REQUEST = 'LOAD_USER_REQUEST'; | ||
21 | +export const LOAD_ME_SUCCESS = 'LOAD_USER_SUCCESS'; | ||
22 | +export const LOAD_ME_FAILURE = 'LOAD_USER_FAILURE'; | ||
23 | + | ||
24 | +export const LOG_OUT_REQUEST = 'LOG_OUT_REQUEST'; | ||
25 | +export const LOG_OUT_SUCCESS = 'LOG_OUT_SUCCESS'; | ||
26 | +export const LOG_OUT_FAILURE = 'LOG_OUT_FAILURE'; | ||
27 | + | ||
28 | +export default (state = initialState, action) => { | ||
29 | + switch (action.type) { | ||
30 | + | ||
31 | + case LOG_IN_REQUEST: { | ||
32 | + return { | ||
33 | + ...state, | ||
34 | + isLoggingIn: true, | ||
35 | + } | ||
36 | + } | ||
37 | + | ||
38 | + case LOG_IN_SUCCESS: { | ||
39 | + const {me} = action.data; | ||
40 | + return { | ||
41 | + ...state, | ||
42 | + me, | ||
43 | + isLoggingIn: false, | ||
44 | + }; | ||
45 | + } | ||
46 | + | ||
47 | + case LOG_IN_FAILURE: { | ||
48 | + const {info} = action.data; | ||
49 | + return { | ||
50 | + ...state, | ||
51 | + isLoggingIn: false, | ||
52 | + info, | ||
53 | + } | ||
54 | + } | ||
55 | + | ||
56 | + case SIGN_UP_REQUEST: { | ||
57 | + return { | ||
58 | + ...state, | ||
59 | + isSigningUp: true | ||
60 | + } | ||
61 | + } | ||
62 | + case SIGN_UP_SUCCESS: { | ||
63 | + const {me} = action.data; | ||
64 | + return { | ||
65 | + ...state, | ||
66 | + me, | ||
67 | + isSigningUp: false, | ||
68 | + }; | ||
69 | + } | ||
70 | + case SIGN_UP_FAILURE: { | ||
71 | + const {info} = action.data; | ||
72 | + return { | ||
73 | + ...state, | ||
74 | + isSigningUp: false, | ||
75 | + info | ||
76 | + }; | ||
77 | + } | ||
78 | + | ||
79 | + case LOAD_ME_REQUEST: { | ||
80 | + return { | ||
81 | + ...state, | ||
82 | + isLoadingMe: true | ||
83 | + } | ||
84 | + } | ||
85 | + case LOAD_ME_SUCCESS: { | ||
86 | + const {user} = action.data; | ||
87 | + console.log(user); | ||
88 | + return { | ||
89 | + ...state, | ||
90 | + me: user, | ||
91 | + isLoadingMe: false | ||
92 | + } | ||
93 | + } | ||
94 | + case LOAD_ME_FAILURE: { | ||
95 | + const {info} = action.data; | ||
96 | + return { | ||
97 | + ...state, | ||
98 | + isLoadingMe: false, | ||
99 | + info | ||
100 | + } | ||
101 | + } | ||
102 | + | ||
103 | + | ||
104 | + case LOG_OUT_REQUEST: { | ||
105 | + return { | ||
106 | + ...state, | ||
107 | + isLoggingOut: true | ||
108 | + } | ||
109 | + } | ||
110 | + | ||
111 | + case LOG_OUT_SUCCESS: { | ||
112 | + console.log('LOG_OUT_SUCCESS 완료'); | ||
113 | + return { | ||
114 | + ...state, | ||
115 | + me: null, | ||
116 | + isLoggingOut: false | ||
117 | + } | ||
118 | + } | ||
119 | + | ||
120 | + case LOG_OUT_FAILURE: { | ||
121 | + const {info} = action.data; | ||
122 | + return { | ||
123 | + ...state, | ||
124 | + isLoggingOut: false, | ||
125 | + info | ||
126 | + } | ||
127 | + } | ||
128 | + | ||
129 | + default: { | ||
130 | + return { | ||
131 | + ...state, | ||
132 | + }; | ||
133 | + } | ||
134 | + } | ||
135 | +}; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
File mode changed
1 | +import {all, call, fork, delay, put, takeEvery, takeLatest} from 'redux-saga/effects'; | ||
2 | +import axios from 'axios'; | ||
3 | +import host from '../env'; | ||
4 | +import { | ||
5 | + LOG_IN_FAILURE, | ||
6 | + LOG_IN_REQUEST, | ||
7 | + LOG_IN_SUCCESS, | ||
8 | + | ||
9 | + SIGN_UP_FAILURE, | ||
10 | + SIGN_UP_REQUEST, | ||
11 | + SIGN_UP_SUCCESS, | ||
12 | + | ||
13 | + LOAD_ME_REQUEST, | ||
14 | + LOAD_ME_SUCCESS, | ||
15 | + LOAD_ME_FAILURE, | ||
16 | + | ||
17 | + LOG_OUT_REQUEST, | ||
18 | + LOG_OUT_SUCCESS, | ||
19 | + LOG_OUT_FAILURE, | ||
20 | +} from '../reducers/user'; | ||
21 | +import {AsyncStorage} from 'react-native'; | ||
22 | + | ||
23 | +const parseCookies = (cookies = '') => | ||
24 | + cookies | ||
25 | + .split(';') | ||
26 | + .map(v => | ||
27 | + v.split('=') | ||
28 | + ) | ||
29 | + .reduce((acc, [key, value]) => { | ||
30 | + acc[key.trim()] = decodeURIComponent(value); | ||
31 | + console.log(acc); | ||
32 | + return acc; | ||
33 | + }, {}); | ||
34 | + | ||
35 | +//로그인 | ||
36 | +function loginAPI(data) { | ||
37 | + const {email, password} = data; | ||
38 | + console.log(email, password); | ||
39 | + console.log(`http://${host}:4001/user/login`); | ||
40 | + return axios.post(`http://${host}:4001/user/login`, { | ||
41 | + email, password | ||
42 | + }, { | ||
43 | + withCredentials: true | ||
44 | + }); | ||
45 | +} | ||
46 | + | ||
47 | +// # 함수의 동기적인 호출을 할 때 사용 | ||
48 | +// 응답이 다 받아진 후에 실행할 때 사용 | ||
49 | +function* login(action) { | ||
50 | + try { | ||
51 | + console.log('login하러 왔어요'); | ||
52 | + const res = yield call(loginAPI, action.data); | ||
53 | + console.log('서버 login에서 온 응답', res); | ||
54 | + const {user} = res.data; | ||
55 | + const cookieArray = res.headers['set-cookie']; | ||
56 | + console.log(cookieArray); | ||
57 | + yield call(AsyncStorage.setItem, 'cookie', `userChecker=s%3A${cookieArray.map(cookie => parseCookies(cookie)['userChecker'].substring(2))}`); | ||
58 | + yield put({ | ||
59 | + type: LOG_IN_SUCCESS, | ||
60 | + data: { | ||
61 | + me: user | ||
62 | + } | ||
63 | + }) | ||
64 | + } catch (e) { | ||
65 | + console.error(e); | ||
66 | + yield put({ | ||
67 | + type: LOG_IN_FAILURE, | ||
68 | + data: { | ||
69 | + info: e.response.data.info | ||
70 | + } | ||
71 | + }) | ||
72 | + } | ||
73 | +}; | ||
74 | + | ||
75 | +function* watchLogin() { | ||
76 | + yield takeLatest(LOG_IN_REQUEST, login); | ||
77 | +} | ||
78 | + | ||
79 | +// 회원가입 | ||
80 | +function signUpAPI(data) { | ||
81 | + const {email, nickName, password} = data; | ||
82 | + return axios.post(`http://${host}:4001/user/signUp`, { | ||
83 | + email, nickName, password | ||
84 | + }, { | ||
85 | + withCredentials: true | ||
86 | + }); | ||
87 | +} | ||
88 | + | ||
89 | +function* signUp(action) { | ||
90 | + try { | ||
91 | + console.log('signUp 실행원할'); | ||
92 | + const res = yield call(signUpAPI, action.data); | ||
93 | + const {me} = res.data; | ||
94 | + yield put({ | ||
95 | + type: SIGN_UP_SUCCESS, | ||
96 | + data: { | ||
97 | + me | ||
98 | + } | ||
99 | + }); | ||
100 | + } catch (e) { | ||
101 | + console.error(e); | ||
102 | + yield put({ | ||
103 | + type: SIGN_UP_FAILURE, | ||
104 | + data: { | ||
105 | + info: e.response.data.info | ||
106 | + } | ||
107 | + }); | ||
108 | + } | ||
109 | +} | ||
110 | + | ||
111 | +// # generator 함수에서 마지막 액션 하나만 유효하다고 인정 | ||
112 | +// 실수로 회원가입 버튼을 연달아 누를 경우 서버의 요청이 2번 가지 않게함 | ||
113 | +function* watchSignUp() { | ||
114 | + yield takeLatest(SIGN_UP_REQUEST, signUp); | ||
115 | +} | ||
116 | + | ||
117 | + | ||
118 | +function loadMeAPI() { | ||
119 | + return axios.get(`http://${host}:4001/user/loadMe`, {withCredentials: true}) | ||
120 | +}; | ||
121 | + | ||
122 | +function* loadMe(action) { | ||
123 | + try { | ||
124 | + console.log('loadMe 실행원할'); | ||
125 | + const res = yield call(loadMeAPI, action.data); | ||
126 | + const {user} = res.data; | ||
127 | + yield put({ | ||
128 | + type: LOAD_ME_SUCCESS, | ||
129 | + data: { | ||
130 | + user | ||
131 | + } | ||
132 | + }) | ||
133 | + } catch (e) { | ||
134 | + console.error(e); | ||
135 | + yield put({ | ||
136 | + type: LOAD_ME_FAILURE, | ||
137 | + data: { | ||
138 | + info: e.response.data.info | ||
139 | + } | ||
140 | + }) | ||
141 | + } | ||
142 | +}; | ||
143 | + | ||
144 | +function* watchLoadMe() { | ||
145 | + yield takeLatest(LOAD_ME_REQUEST, loadMe); | ||
146 | +} | ||
147 | + | ||
148 | +function logoutAPI() { | ||
149 | + return axios.get(`http://${host}:4001/user/logout`, {withCredentials: true}); | ||
150 | +} | ||
151 | + | ||
152 | +function* logout() { | ||
153 | + try { | ||
154 | + const res = yield call(logoutAPI); | ||
155 | + console.log('logout 완료'); | ||
156 | + yield call(AsyncStorage.removeItem, 'cookie'); | ||
157 | + yield put({ | ||
158 | + type: LOG_OUT_SUCCESS | ||
159 | + }); | ||
160 | + } catch (e) { | ||
161 | + console.error(e); | ||
162 | + yield put({ | ||
163 | + type: LOG_OUT_FAILURE, | ||
164 | + data: { | ||
165 | + info: e.response.data.info | ||
166 | + } | ||
167 | + }) | ||
168 | + } | ||
169 | +} | ||
170 | + | ||
171 | +function* watchLogoutMe() { | ||
172 | + yield takeLatest(LOG_OUT_REQUEST, logout); | ||
173 | +} | ||
174 | + | ||
175 | +// # 모든 액션을 유효하게 인정한다. | ||
176 | +// while(true)로 감싸는 효과 | ||
177 | +// takeEvery | ||
178 | + | ||
179 | +// # 함수의 비동기적인 호출을 사용할 때 | ||
180 | +// call과 다르게 fork는 순서 상관없이 실행할 때 사용 | ||
181 | +export default function* userSaga() { | ||
182 | + yield all([ | ||
183 | + fork(watchLogin), | ||
184 | + fork(watchSignUp), | ||
185 | + fork(watchLoadMe), | ||
186 | + fork(watchLogoutMe), | ||
187 | + ]); | ||
188 | +} | ||
189 | + |
1 | +import React from 'react'; | ||
2 | +import {View, Text, Button} from 'react-native'; | ||
3 | +import {useNavigation} from "@react-navigation/native"; | ||
4 | + | ||
5 | +const Gallery = () => { | ||
6 | + const navigation = useNavigation(); | ||
7 | + | ||
8 | + return ( | ||
9 | + <View> | ||
10 | + <Text> | ||
11 | + 하이하이 | ||
12 | + </Text> | ||
13 | + </View> | ||
14 | + ) | ||
15 | +}; | ||
16 | + | ||
17 | +export default Gallery; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +import React, {useState, useContext, useEffect, useCallback} from 'react'; | ||
2 | +import {View, Text, Button, TextInput, TouchableOpacity} from 'react-native'; | ||
3 | +import {useDispatch, useSelector} from "react-redux"; | ||
4 | +import {LOG_IN_REQUEST, LOG_OUT_REQUEST} from "../reducers/user"; | ||
5 | +import {MaterialCommunityIcons} from "@expo/vector-icons"; | ||
6 | +import styled from "styled-components"; | ||
7 | +import {useNavigation} from '@react-navigation/native'; | ||
8 | +import LoadingComponent from "../components/LoadingComponent"; | ||
9 | +import MyProfileComponent from "../components/MyProfileComponent"; | ||
10 | +import LoginComponent from "../components/LoginComponent"; | ||
11 | + | ||
12 | +const Login = () => { | ||
13 | + const navigation = useNavigation(); | ||
14 | + const [loading, setLoading] = useState(true); | ||
15 | + | ||
16 | + const {me} = useSelector(state => state.user); | ||
17 | + const {isLoggingIn} = useSelector(state => state.user); | ||
18 | + | ||
19 | + | ||
20 | + useEffect(() => { | ||
21 | + setLoading(false); | ||
22 | + }, [me]); | ||
23 | + | ||
24 | + return ( | ||
25 | + <View> | ||
26 | + {me ? | ||
27 | + <MyProfileComponent/> | ||
28 | + : | ||
29 | + <LoginComponent/> | ||
30 | + } | ||
31 | + </View> | ||
32 | + ) | ||
33 | +}; | ||
34 | + | ||
35 | +export default Login; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +import React from 'react'; | ||
2 | +import {View, Text, TouchableOpacity} from 'react-native'; | ||
3 | +import styled from "styled-components"; | ||
4 | +import {useNavigation} from "@react-navigation/native"; | ||
5 | +import {useSelector} from "react-redux"; | ||
6 | + | ||
7 | + | ||
8 | +const GoToGalleryButton = styled.TouchableOpacity` | ||
9 | + width: 60px; | ||
10 | + border: 1px; | ||
11 | + | ||
12 | + align-items: center; | ||
13 | + justify-content: center; | ||
14 | + | ||
15 | + flex: 2 | ||
16 | +`; | ||
17 | + | ||
18 | +const GoToSelectOrTakePhotoButton = styled.TouchableOpacity` | ||
19 | + position: absolute; | ||
20 | + right: 20px; | ||
21 | + bottom: 20px; | ||
22 | + width: 30px; | ||
23 | + height: 30px; | ||
24 | + border-radius: 50px; | ||
25 | + border: 15px solid green; | ||
26 | +`; | ||
27 | + | ||
28 | +const Main = () => { | ||
29 | + const navigation = useNavigation(); | ||
30 | + | ||
31 | + const goToGallery = () => { | ||
32 | + console.log(navigation.navigate); | ||
33 | + navigation.navigate('Gallery'); | ||
34 | + }; | ||
35 | + | ||
36 | + const goToSelectOrTakePhoto = () => { | ||
37 | + navigation.navigate('SelectOrTakePhotoStackNavigation'); | ||
38 | + }; | ||
39 | + | ||
40 | + | ||
41 | + const {me} = useSelector(state => state.user); | ||
42 | + | ||
43 | + | ||
44 | + return ( | ||
45 | + <> | ||
46 | + <View style={{ | ||
47 | + flex: 1, | ||
48 | + alignItems: 'center', | ||
49 | + justifyContent: 'center' | ||
50 | + }}> | ||
51 | + <GoToGalleryButton title={'갤러리로 가보자'} onPress={goToGallery}> | ||
52 | + <Text>갤러리로 가보자</Text> | ||
53 | + </GoToGalleryButton> | ||
54 | + <View style={{ | ||
55 | + flex: 8, | ||
56 | + flexDirection: 'row' | ||
57 | + }}> | ||
58 | + <Text style={{ | ||
59 | + width: '100%', | ||
60 | + flex: 1, | ||
61 | + backgroundColor: 'red' | ||
62 | + }}>메인페이지</Text> | ||
63 | + <Text style={{ | ||
64 | + width: '100%', | ||
65 | + flex: 1, | ||
66 | + backgroundColor: 'grey', | ||
67 | + }}>메인페이지2</Text> | ||
68 | + <GoToSelectOrTakePhotoButton onPress={goToSelectOrTakePhoto}/> | ||
69 | + </View> | ||
70 | + </View> | ||
71 | + </> | ||
72 | + ) | ||
73 | +}; | ||
74 | + | ||
75 | +export default Main; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +import React from 'react'; | ||
2 | +import MapView from 'react-native-maps'; | ||
3 | +import {StyleSheet, Text, View, Dimensions} from 'react-native'; | ||
4 | +import screen from '../constants/layout'; | ||
5 | + | ||
6 | + | ||
7 | +const Maps = () => { | ||
8 | + return ( | ||
9 | + <View style={styles.container}> | ||
10 | + <MapView | ||
11 | + style={styles.mapStyle} | ||
12 | + initialRegion={{ | ||
13 | + latitude: 37.78825, | ||
14 | + longitude: -122.4324, | ||
15 | + latitudeDelta: 0.0922, | ||
16 | + longitudeDelta: 0.0421 | ||
17 | + }} | ||
18 | + /> | ||
19 | + </View> | ||
20 | + ) | ||
21 | +}; | ||
22 | + | ||
23 | +export default Maps; | ||
24 | + | ||
25 | +const styles = StyleSheet.create({ | ||
26 | + container: { | ||
27 | + flex: 1, | ||
28 | + backgroundColor: '#fff', | ||
29 | + alignItems: 'center', | ||
30 | + }, | ||
31 | + mapStyle: { | ||
32 | + width: screen.width, | ||
33 | + height: screen.height / 2, | ||
34 | + }, | ||
35 | +}); |
1 | +import React from 'react'; | ||
2 | +import {View, Text, Button} from 'react-native'; | ||
3 | +import SignUpComponent from "../components/SignUpComponent"; | ||
4 | + | ||
5 | +const Profile = () => { | ||
6 | + const {me} = (state => state.user); | ||
7 | + return ( | ||
8 | + <View> | ||
9 | + <SignUpComponent/> | ||
10 | + </View> | ||
11 | + ) | ||
12 | +}; | ||
13 | + | ||
14 | +export default Profile; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +import React, {useEffect, useState} from 'react'; | ||
2 | +import * as MediaLibrary from 'expo-media-library'; | ||
3 | +import * as Permission from 'expo-permissions'; | ||
4 | +import {Image, ImageBackground, ScrollView, View, Text, TouchableOpacity} from "react-native"; | ||
5 | +import {useNavigation} from '@react-navigation/native'; | ||
6 | +import LoadingComponent from "../components/LoadingComponent"; | ||
7 | +import screen from '../constants/layout'; | ||
8 | +import {UploadPhoto} from './UploadPhoto'; | ||
9 | + | ||
10 | +const SelectPhoto = (props) => { | ||
11 | + const navigation = useNavigation(); | ||
12 | + const [loading, setLoading] = useState(false); | ||
13 | + const [hasPermission, setHasPermission] = useState(true); | ||
14 | + const [selectedPhoto, setSelectedPhoto] = useState([]); | ||
15 | + const [allPhotos, setAllPhotos] = useState([]); | ||
16 | + | ||
17 | + const getPhotos = async () => { | ||
18 | + try { | ||
19 | + const {assets} = await MediaLibrary.getAssetsAsync(); | ||
20 | + const [firstPhoto] = assets; | ||
21 | + setSelectedPhoto([firstPhoto]); | ||
22 | + setAllPhotos(assets); | ||
23 | + } catch (e) { | ||
24 | + console.error(e) | ||
25 | + } finally { | ||
26 | + setLoading(false); | ||
27 | + } | ||
28 | + }; | ||
29 | + | ||
30 | + const askPermission = async () => { | ||
31 | + try { | ||
32 | + setLoading(true); | ||
33 | + const {status} = await Permission.askAsync(Permission.CAMERA_ROLL); | ||
34 | + console.log(status); | ||
35 | + if (status === 'granted') { | ||
36 | + setHasPermission(true); | ||
37 | + await getPhotos(); | ||
38 | + } | ||
39 | + } catch (e) { | ||
40 | + console.error(e); | ||
41 | + setHasPermission(false); | ||
42 | + } | ||
43 | + }; | ||
44 | + | ||
45 | + const changeSelectedPhoto = (photo) => { | ||
46 | + setSelectedPhoto([photo]); | ||
47 | + }; | ||
48 | + | ||
49 | + const uploadPhoto = () => { | ||
50 | + navigation.navigate('UploadPhoto', {photos: selectedPhoto}) | ||
51 | + }; | ||
52 | + | ||
53 | + useEffect(() => { | ||
54 | + askPermission(); | ||
55 | + return () => { | ||
56 | + setLoading(true); | ||
57 | + setHasPermission(false); | ||
58 | + setSelectedPhoto([]); | ||
59 | + setAllPhotos([]); | ||
60 | + } | ||
61 | + }, []); | ||
62 | + | ||
63 | + return ( | ||
64 | + <View> | ||
65 | + {loading | ||
66 | + ? | ||
67 | + <LoadingComponent/> | ||
68 | + : | ||
69 | + hasPermission | ||
70 | + ? | ||
71 | + selectedPhoto[0] | ||
72 | + ? | ||
73 | + <> | ||
74 | + <ImageBackground | ||
75 | + style={{width: screen.width, height: screen.height / 2}} | ||
76 | + source={{uri: selectedPhoto[0].uri}} | ||
77 | + > | ||
78 | + <TouchableOpacity | ||
79 | + style={{ | ||
80 | + justifyContent: 'center', | ||
81 | + alignItems: 'center', | ||
82 | + }} | ||
83 | + key={selectedPhoto.id} | ||
84 | + onPress={uploadPhoto} | ||
85 | + > | ||
86 | + <Text>선택</Text> | ||
87 | + </TouchableOpacity> | ||
88 | + </ImageBackground> | ||
89 | + | ||
90 | + <ScrollView> | ||
91 | + | ||
92 | + <> | ||
93 | + <ScrollView horizontal contentContainerStyle={{flexDirection: 'row'}}> | ||
94 | + {allPhotos.map(photo => { | ||
95 | + return ( | ||
96 | + <TouchableOpacity | ||
97 | + key={photo.id} | ||
98 | + onPress={() => changeSelectedPhoto(photo)}> | ||
99 | + <Image | ||
100 | + source={{uri: photo.uri}} | ||
101 | + style={{ | ||
102 | + width: screen.width / 3, | ||
103 | + height: screen.height / 4, | ||
104 | + opacity: photo.id === selectedPhoto[0].id ? 0.6 : 1 | ||
105 | + }}/> | ||
106 | + </TouchableOpacity> | ||
107 | + ) | ||
108 | + } | ||
109 | + )} | ||
110 | + </ScrollView> | ||
111 | + </> | ||
112 | + | ||
113 | + </ScrollView> | ||
114 | + </> | ||
115 | + : | ||
116 | + null | ||
117 | + : | ||
118 | + <Text>사용자 권한이 없습니다</Text> | ||
119 | + } | ||
120 | + </View> | ||
121 | + ) | ||
122 | +}; | ||
123 | + | ||
124 | +export default SelectPhoto; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +import React, {useEffect, useState, useRef} from 'react'; | ||
2 | +import * as MediaLibrary from 'expo-media-library'; | ||
3 | +import * as Permission from 'expo-permissions'; | ||
4 | +import {Image, ImageBackground, ScrollView, View, Text, TouchableOpacity} from "react-native"; | ||
5 | +import {useNavigation} from '@react-navigation/native'; | ||
6 | +import LoadingComponent from "../components/LoadingComponent"; | ||
7 | +import screen from '../constants/layout'; | ||
8 | +import { Camera } from 'expo-camera'; | ||
9 | +import styled from "styled-components"; | ||
10 | +import {MaterialCommunityIcons} from "@expo/vector-icons"; | ||
11 | + | ||
12 | +const TakePhotoButton = styled.TouchableOpacity` | ||
13 | + width: 70px; | ||
14 | + height: 70px; | ||
15 | + border-radius: 50px; | ||
16 | + border: 15px solid green; | ||
17 | +`; | ||
18 | + | ||
19 | + | ||
20 | +const TakePhoto = (props) => { | ||
21 | + const navigation = useNavigation(); | ||
22 | + const [loading, setLoading] = useState(false); | ||
23 | + const [hasPermission, setHasPermission] = useState(false); | ||
24 | + const [cameraType, setCameraType] = useState(Camera.Constants.Type.back); | ||
25 | + const [canTakePhoto, setCanTakePhoto] = useState(true); | ||
26 | + const cameraRef = useRef(null); | ||
27 | + | ||
28 | + | ||
29 | + const askPermission = async () => { | ||
30 | + try { | ||
31 | + setLoading(true); | ||
32 | + const {status} = await Permission.askAsync(Permission.CAMERA); | ||
33 | + console.log(status); | ||
34 | + if (status === 'granted') { | ||
35 | + setHasPermission(true); | ||
36 | + } | ||
37 | + } catch (e) { | ||
38 | + console.error(e); | ||
39 | + setHasPermission(false); | ||
40 | + } finally { | ||
41 | + setLoading(false) | ||
42 | + } | ||
43 | + }; | ||
44 | + | ||
45 | + const changeCameraType = () => { | ||
46 | + if (cameraType === Camera.Constants.Type.front) { | ||
47 | + setCameraType(Camera.Constants.Type.back); | ||
48 | + } else { | ||
49 | + setCameraType(Camera.Constants.Type.front); | ||
50 | + } | ||
51 | + }; | ||
52 | + | ||
53 | + const takePhoto = async () => { | ||
54 | + if (!canTakePhoto) { | ||
55 | + return | ||
56 | + } | ||
57 | + try { | ||
58 | + setCanTakePhoto(false); | ||
59 | + const {uri} = await cameraRef.current.takePictureAsync({quality: 1}); | ||
60 | + const asset = await MediaLibrary.createAssetAsync(uri); | ||
61 | + navigation.navigate('UploadPhoto', {photo: asset}); | ||
62 | + } catch (e) { | ||
63 | + console.error(e); | ||
64 | + setCanTakePhoto(true); | ||
65 | + } | ||
66 | + }; | ||
67 | + | ||
68 | + const goUpload = () => { | ||
69 | + navigation.navigate('UploadPhoto'); | ||
70 | + }; | ||
71 | + | ||
72 | + useEffect(() => { | ||
73 | + askPermission(); | ||
74 | + }, []); | ||
75 | + | ||
76 | + return ( | ||
77 | + <View style={{alignItems: 'center'}}> | ||
78 | + {loading | ||
79 | + ? <LoadingComponent/> | ||
80 | + : hasPermission ? | ||
81 | + <View> | ||
82 | + <Camera | ||
83 | + ref={cameraRef} | ||
84 | + type={cameraType} | ||
85 | + style={{ | ||
86 | + justifyContent: 'flex-end', | ||
87 | + padding: 10, | ||
88 | + width: screen.width, | ||
89 | + height: screen.height / 2 | ||
90 | + }}> | ||
91 | + <TouchableOpacity onPress={changeCameraType}> | ||
92 | + <MaterialCommunityIcons color={'green'} name={'camera'} size={24}/> | ||
93 | + </TouchableOpacity> | ||
94 | + </Camera> | ||
95 | + <TakePhotoButton | ||
96 | + onPress={takePhoto} | ||
97 | + disabled={!canTakePhoto} | ||
98 | + /> | ||
99 | + <TakePhotoButton | ||
100 | + onPress={goUpload} | ||
101 | + /> | ||
102 | + </View> | ||
103 | + : | ||
104 | + null | ||
105 | + } | ||
106 | + </View> | ||
107 | + ) | ||
108 | +}; | ||
109 | + | ||
110 | +export default TakePhoto; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +import React from 'react'; | ||
2 | +import {View, Text, Image, Button, StyleSheet} from 'react-native'; | ||
3 | +import styled from "styled-components"; | ||
4 | + | ||
5 | +const UploadPhoto = (props) => { | ||
6 | + const {route} = props; | ||
7 | + const {photos} = route.params; | ||
8 | + | ||
9 | + | ||
10 | + return ( | ||
11 | + <View> | ||
12 | + <Text> | ||
13 | + 하이하이 | ||
14 | + </Text> | ||
15 | + <View>{photos.map((photo, index) => { | ||
16 | + return ( | ||
17 | + <Image style={{width: 200, height: 200}} source={{uri: photo.uri}} key={photo.id}/>) | ||
18 | + })}</View> | ||
19 | + | ||
20 | + </View> | ||
21 | + ) | ||
22 | +} | ||
23 | +export default UploadPhoto; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
code/render_server_react_native/store.js
0 → 100644
1 | +import createSagaMiddleware from "redux-saga"; | ||
2 | +import {applyMiddleware, compose, createStore} from "redux"; | ||
3 | +import rootReducer from "./reducers"; | ||
4 | +import rootSaga from "./sagas"; | ||
5 | + | ||
6 | +const sagaMiddleware = createSagaMiddleware(); | ||
7 | +const middlewares = [sagaMiddleware]; | ||
8 | +const enhancer = compose( | ||
9 | + applyMiddleware(...middlewares), | ||
10 | + // !options.isServer && typeof window.REDUX_DEVTOOLS_EXTENSION !== 'undefined' ? window.REDUX_DEVTOOLS_EXTENSION() : (f) => f, | ||
11 | +); | ||
12 | +const store = createStore(rootReducer, enhancer); | ||
13 | +sagaMiddleware.run(rootSaga); | ||
14 | + | ||
15 | +export default store; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
code/render_server_react_native/yarn.lock
0 → 100644
This diff could not be displayed because it is too large.
code/user_server/.env
0 → 100644
code/user_server/.idea/.gitignore
0 → 100644
code/user_server/.idea/misc.xml
0 → 100644
code/user_server/.idea/modules.xml
0 → 100644
1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
2 | +<project version="4"> | ||
3 | + <component name="ProjectModuleManager"> | ||
4 | + <modules> | ||
5 | + <module fileurl="file://$PROJECT_DIR$/.idea/user_and_post_server.iml" filepath="$PROJECT_DIR$/.idea/user_and_post_server.iml" /> | ||
6 | + </modules> | ||
7 | + </component> | ||
8 | +</project> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
2 | +<module type="WEB_MODULE" version="4"> | ||
3 | + <component name="NewModuleRootManager"> | ||
4 | + <content url="file://$MODULE_DIR$" /> | ||
5 | + <orderEntry type="inheritedJdk" /> | ||
6 | + <orderEntry type="sourceFolder" forTests="false" /> | ||
7 | + </component> | ||
8 | +</module> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
code/user_server/.idea/vcs.xml
0 → 100644
code/user_server/app.js
0 → 100644
1 | +const path = require('path'); | ||
2 | +const morgan = require('morgan'); | ||
3 | +const express = require('express'); | ||
4 | +//header Cookie : 쿠키, 해쉬함수 | ||
5 | +//오리지널 >> 해쉬 >> 압축메세지 | ||
6 | + | ||
7 | +const cookieParser = require('cookie-parser'); | ||
8 | +const expressSession = require('express-session'); | ||
9 | +const passport = require('passport'); | ||
10 | +const httpErrors = require('http-errors'); | ||
11 | +const dotenv = require('dotenv'); | ||
12 | +dotenv.config(); | ||
13 | +const MongoStore = require('connect-mongo')(expressSession); | ||
14 | +const MONGO_URL = `mongodb://localhost:27017/admin`; | ||
15 | +const cors = require('cors'); | ||
16 | + | ||
17 | + | ||
18 | +const {sequelize} = require('./models/index'); | ||
19 | +sequelize.sync({force: false}); | ||
20 | +const connect = require('./schemas/index'); | ||
21 | +connect(); | ||
22 | + | ||
23 | +const sessionMiddleware = expressSession({ | ||
24 | + resave: false, | ||
25 | + saveUninitialized: false, | ||
26 | + secret: process.env.COOKIE_SECRET, | ||
27 | + cookie: { | ||
28 | + httpOnly: true, | ||
29 | + secure: false | ||
30 | + }, | ||
31 | + name: 'userChecker', | ||
32 | + store: new MongoStore({ | ||
33 | + url: MONGO_URL, | ||
34 | + collection: "sessions" | ||
35 | + }), | ||
36 | +}); | ||
37 | + | ||
38 | +const passportIndex = require('./passport/index'); | ||
39 | +const userRouter = require('./routes/user'); | ||
40 | + | ||
41 | +passportIndex(passport); | ||
42 | + | ||
43 | + | ||
44 | +// const app = express(); 사용 설명서 | ||
45 | +// app.use(미들웨어) | ||
46 | +// 미들웨어란: (req, res, next) => {req와 res를 분석 및 가공, next로 req와 res를 다음 미들웨어로 전달} | ||
47 | +// 따라서 미들웨어끼리의 순서가 중요하다 | ||
48 | + | ||
49 | +// 어떤 미들웨어에서 req에 변수를 등록하고, 다음 미들웨어에서 그 변수를 가져다가 사용하는 방법 | ||
50 | +// 1. req.set()으로 변수를 등록하고, req.get()으로 전역 변수들을 가져와서 사용할 수 있다 | ||
51 | +// 2. app.set()으로 변수를 등록하고, req.app.get()으로 전역 변수들을 가져와서 사용할 수 있다(req 객체에 app 객체는 자동으로 세팅된다) | ||
52 | +// 3. req.app.set()으로 변수를 등록하고, req.app.get()으로 전역 변수들을 가져와서 사용할 수 있다 | ||
53 | + | ||
54 | +// res 사용법 | ||
55 | +// 오리지날: res.writeHead, res.write, res.end | ||
56 | +// 익스프레스 프레임워크: res.render('view 파일 이름', 자바스크립트 변수 객체), res.send(아무거나), res,json(객체), res.redirect('경로'), res.sendFile, | ||
57 | + | ||
58 | +const app = express(); // 익스프레스 프레임워크를 사용하기 위한 app 객체를 생성 | ||
59 | + | ||
60 | +app.use(morgan('dev')); // 로거를 미들웨어 최상단에 위치시켜서 서버로 들어오는 모든 요청에 대한 로그를 콘솔에서 확인 | ||
61 | + | ||
62 | +app.use(cors({ | ||
63 | + origin: 'http://localhost:3001', | ||
64 | + credentials: true | ||
65 | +})); | ||
66 | +app.use(express.json()); | ||
67 | +app.use(express.urlencoded({extended: false})); | ||
68 | +app.use(cookieParser(process.env.COOKIE_SECRET)); | ||
69 | +app.use(sessionMiddleware); | ||
70 | + | ||
71 | +app.use(passport.initialize()); // 패스포트 작동 시작 | ||
72 | +app.use(passport.session()); // 패스포트 세션 작업 | ||
73 | + | ||
74 | + | ||
75 | +app.use('/public', express.static(path.join(__dirname, 'open'))); // 모두에게 공개된 폴더 설정 | ||
76 | +app.use('/user', userRouter); | ||
77 | + | ||
78 | +app.use(function (req, res, next) { | ||
79 | + next(httpErrors(404)); | ||
80 | +}); | ||
81 | +app.use(function (err, req, res, next) { | ||
82 | + // set locals, only providing error in development | ||
83 | + console.log(req); | ||
84 | + res.locals.message = err.message; | ||
85 | + res.locals.error = req.app.get('env') === 'development' ? err : {}; | ||
86 | + | ||
87 | + //render the error page | ||
88 | + res.status(err.status || 500); | ||
89 | + res.render('error'); | ||
90 | +}); | ||
91 | + | ||
92 | +module.exports = { | ||
93 | + app, | ||
94 | + sessionMiddleware | ||
95 | +}; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
code/user_server/bin/www
0 → 100644
1 | +#!/usr/bin/env node | ||
2 | + | ||
3 | + | ||
4 | +/* | ||
5 | +* Module dependencies | ||
6 | +*/ | ||
7 | + | ||
8 | +const debug = require('debug')('node_study_project_final:server'); | ||
9 | +const http = require('http'); | ||
10 | +const {app} = require('../app'); | ||
11 | + | ||
12 | + | ||
13 | +/* | ||
14 | +* Get port from environment and store in Express. | ||
15 | +*/ | ||
16 | + | ||
17 | +const port = normalizePort(process.env.PORT || '3001'); | ||
18 | +app.set('port', port); | ||
19 | + | ||
20 | +/* | ||
21 | +* Create HTTP server. | ||
22 | +*/ | ||
23 | + | ||
24 | +const server = http.createServer(app); | ||
25 | + | ||
26 | +function normalizePort(val) { | ||
27 | + const port = parseInt(val, 10); | ||
28 | + | ||
29 | + if (isNaN(port)) { | ||
30 | + return val; | ||
31 | + } | ||
32 | + | ||
33 | + if (port >= 0) { | ||
34 | + return port; | ||
35 | + } | ||
36 | + | ||
37 | + return false; | ||
38 | +}; | ||
39 | + | ||
40 | +const onListening = () => { | ||
41 | + const addr = server.address(); | ||
42 | + const bind = typeof addr === 'string' | ||
43 | + ? 'pipe ' + addr | ||
44 | + : 'port ' + addr.port; | ||
45 | + debug('Listening on ' + bind); | ||
46 | +}; | ||
47 | + | ||
48 | +const onError = (error) => { | ||
49 | + if (error.syscall !== 'listen') { | ||
50 | + throw error; | ||
51 | + } | ||
52 | + | ||
53 | + const bind = typeof port === 'string' | ||
54 | + ? 'Pipe ' + port | ||
55 | + : 'Port ' + port; | ||
56 | + | ||
57 | + switch (error.code) { | ||
58 | + case 'EACCES': | ||
59 | + console.error(bind + ' requires elevated privileges'); | ||
60 | + process.exit(1); | ||
61 | + break; | ||
62 | + case 'EADDRINUSE': | ||
63 | + console.error(bind + ' is already in use'); | ||
64 | + process.exit(1); | ||
65 | + break; | ||
66 | + default: | ||
67 | + throw error; | ||
68 | + } | ||
69 | +}; | ||
70 | + | ||
71 | + | ||
72 | +server.listen(port); | ||
73 | +server.on('listening', onListening); | ||
74 | +server.on('error', onError); | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
code/user_server/config/config.json
0 → 100644
1 | +{ | ||
2 | + "development": { | ||
3 | + "username": "root", | ||
4 | + "password": "ksy98042!", | ||
5 | + "database": "capstone_design_prj1", | ||
6 | + "host": "127.0.0.1", | ||
7 | + "dialect": "mysql", | ||
8 | + "operatorsAliases": false | ||
9 | + }, | ||
10 | + "test": { | ||
11 | + "username": "root", | ||
12 | + "password": null, | ||
13 | + "database": "database_test", | ||
14 | + "host": "127.0.0.1", | ||
15 | + "dialect": "mysql", | ||
16 | + "operatorsAliases": false | ||
17 | + }, | ||
18 | + "production": { | ||
19 | + "username": "root", | ||
20 | + "password": null, | ||
21 | + "database": "database_production", | ||
22 | + "host": "127.0.0.1", | ||
23 | + "dialect": "mysql", | ||
24 | + "operatorsAliases": false | ||
25 | + } | ||
26 | +} |
code/user_server/models/index.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +const fs = require('fs'); | ||
4 | +const path = require('path'); | ||
5 | +const Sequelize = require('sequelize'); | ||
6 | +const basename = path.basename(__filename); | ||
7 | +const env = process.env.NODE_ENV || 'development'; | ||
8 | +const config = require(__dirname + '/../config/config.json')[env]; | ||
9 | +const models = {}; | ||
10 | + | ||
11 | +let sequelize; | ||
12 | +if (config.use_env_variable) { | ||
13 | + sequelize = new Sequelize(process.env[config.use_env_variable], config); | ||
14 | +} else { | ||
15 | + sequelize = new Sequelize(config.database, config.username, config.password, config); | ||
16 | +} | ||
17 | + | ||
18 | +// 반복문을 돌면서 models 내에 있는 파일들을 읽고 그것을 모델로 정의함 | ||
19 | +fs | ||
20 | + .readdirSync(__dirname) | ||
21 | + .filter(file => { | ||
22 | + return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js'); | ||
23 | + }) | ||
24 | + .forEach(file => { | ||
25 | + const model = sequelize['import'](path.join(__dirname, file)); | ||
26 | + models[model.name] = model; | ||
27 | + }); | ||
28 | + | ||
29 | +models.User = require('./user')(sequelize, Sequelize); | ||
30 | + | ||
31 | + | ||
32 | +Object.keys(models).forEach(modelName => { | ||
33 | + if (models[modelName].associate) { | ||
34 | + models[modelName].associate(models); | ||
35 | + } | ||
36 | +}); | ||
37 | + | ||
38 | +models.sequelize = sequelize; | ||
39 | +models.Sequelize = Sequelize; | ||
40 | + | ||
41 | +module.exports = models; |
code/user_server/models/user.js
0 → 100644
1 | +module.exports = (sequelize, DataTypes) => { | ||
2 | + const User = sequelize.define("User", { | ||
3 | + email: { | ||
4 | + type: DataTypes.STRING(30), | ||
5 | + allowNull: false, | ||
6 | + unique: true | ||
7 | + }, | ||
8 | + nickName: { | ||
9 | + type: DataTypes.STRING(10), | ||
10 | + allowNull: false, | ||
11 | + unique: true | ||
12 | + }, | ||
13 | + hashedPassword: { | ||
14 | + type: DataTypes.STRING(200), | ||
15 | + allowNull: false | ||
16 | + } | ||
17 | + }, { | ||
18 | + timestamps: true, | ||
19 | + paranoid: true, | ||
20 | + underscored: false, | ||
21 | + charset: 'utf8mb4', | ||
22 | + collate: 'utf8mb4_general_ci' | ||
23 | + }); | ||
24 | + // User.associate = (models) => { | ||
25 | + // models.User.hasMany(models.SnsId, {onDelete: 'CASCADE', foreignKey: 'userId', sourceKey: 'id'}); | ||
26 | + // models.User.hasMany(models.Post, {onDelete: 'CASCADE', foreignKey: 'userId', sourceKey: 'id'}); | ||
27 | + // models.User.hasMany(models.Comment, {onDelete: 'CASCADE', foreignKey: 'userId', sourceKey: 'id'}); | ||
28 | + // }; | ||
29 | + return User; | ||
30 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
code/user_server/open/stylesheets/style.css
0 → 100644
1 | +body { | ||
2 | + padding: 50px; | ||
3 | + font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; | ||
4 | +} | ||
5 | + | ||
6 | +.card { | ||
7 | + float: left; | ||
8 | + margin: 10px; | ||
9 | + border: 3px solid #e3e3e3; | ||
10 | + width: 300px; | ||
11 | +} | ||
12 | + | ||
13 | +.post-img { | ||
14 | + width: 300px; | ||
15 | +} | ||
16 | + | ||
17 | +.mine { | ||
18 | + background-color: #808B96 ; | ||
19 | +} | ||
20 | + | ||
21 | +.other { | ||
22 | + background-color: #BFC9CA; | ||
23 | +} | ||
24 | + | ||
25 | +.system { | ||
26 | + text-align: center; | ||
27 | +} |
code/user_server/package-lock.json
0 → 100644
This diff could not be displayed because it is too large.
code/user_server/package.json
0 → 100644
1 | +{ | ||
2 | + "name": "capstone_design_prj1", | ||
3 | + "version": "0.0.0", | ||
4 | + "private": true, | ||
5 | + "scripts": { | ||
6 | + "start": "nodemon ./bin/www" | ||
7 | + }, | ||
8 | + "dependencies": { | ||
9 | + "axios": "^0.19.2", | ||
10 | + "bcrypt": "^3.0.6", | ||
11 | + "connect-mongo": "^3.2.0", | ||
12 | + "cookie-parser": "~1.4.4", | ||
13 | + "cookie-signature": "^1.1.0", | ||
14 | + "cors": "^2.8.5", | ||
15 | + "debug": "~2.6.9", | ||
16 | + "dotenv": "^8.1.0", | ||
17 | + "express": "~4.16.1", | ||
18 | + "express-session": "^1.16.2", | ||
19 | + "fs": "0.0.1-security", | ||
20 | + "http-errors": "~1.6.3", | ||
21 | + "mongoose": "^5.9.2", | ||
22 | + "morgan": "^1.9.1", | ||
23 | + "multer": "^1.4.2", | ||
24 | + "mysql": "^2.18.1", | ||
25 | + "mysql2": "^1.7.0", | ||
26 | + "nodemon": "^1.19.4", | ||
27 | + "passport": "^0.4.0", | ||
28 | + "passport-local": "^1.0.0", | ||
29 | + "path": "^0.12.7", | ||
30 | + "pug": "2.0.0-beta11", | ||
31 | + "sequelize": "^5.21.5", | ||
32 | + "sequelize-cli": "^5.5.1", | ||
33 | + "socket.io": "^2.3.0" | ||
34 | + } | ||
35 | +} |
code/user_server/passport/index.js
0 → 100644
1 | +const models = require('../models/index'); | ||
2 | +const localStrategy = require('./localStrategy'); | ||
3 | + | ||
4 | +module.exports = (passport) => { | ||
5 | + passport.serializeUser((user, done) => { | ||
6 | + done(null, user.email); | ||
7 | + }); | ||
8 | + passport.deserializeUser(async (email, done) => { | ||
9 | + try { | ||
10 | + const user = await models.User.findOne({ | ||
11 | + where: {email}, | ||
12 | + attributes: ['id', 'email', 'nickName'] | ||
13 | + }); | ||
14 | + | ||
15 | + if (!user) { | ||
16 | + console.error('유저 데이터가 존재하지 않습니다.'); | ||
17 | + done(null, false, {message: '유저 데이터가 존재하지 않습니다.'}); | ||
18 | + } | ||
19 | + | ||
20 | + return done(null, user); | ||
21 | + } catch (e) { | ||
22 | + console.error(e); | ||
23 | + done(e); | ||
24 | + } | ||
25 | + }); | ||
26 | + localStrategy(passport); | ||
27 | +}; |
code/user_server/passport/localStrategy.js
0 → 100644
1 | +const LocalStrategy = require('passport-local').Strategy; | ||
2 | +const models = require('../models/index'); | ||
3 | +const bcrypt = require("bcrypt"); | ||
4 | + | ||
5 | +module.exports = (passport) => { | ||
6 | + passport.use(new LocalStrategy({ | ||
7 | + usernameField: 'email', | ||
8 | + passwordField: 'password' | ||
9 | + }, async (email, password, done) => { | ||
10 | + try { | ||
11 | + let user = await models.User.findOne({ | ||
12 | + where: {email} | ||
13 | + }); | ||
14 | + if (!user) { | ||
15 | + return done(null, false, {message: "유저 데이터가 존재하지 않습니다."}); | ||
16 | + } | ||
17 | + | ||
18 | + let resultOfPasswordCheck = await bcrypt.compare(password, user.hashedPassword); | ||
19 | + if (!resultOfPasswordCheck) { | ||
20 | + return done(null, false, {message: '비밀번호 에러입니다'}); | ||
21 | + } | ||
22 | + user = await models.User.findOne({ | ||
23 | + where:{email}, | ||
24 | + attributes: ['id', 'email', 'nickName'] | ||
25 | + }); | ||
26 | + return done(null, user); | ||
27 | + } catch (e) { | ||
28 | + console.error(e); | ||
29 | + return done(e); | ||
30 | + } | ||
31 | + }) | ||
32 | + ); | ||
33 | +}; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
code/user_server/routes/index.js
0 → 100644
1 | +const express = require('express'); | ||
2 | +const router = express.Router(); | ||
3 | +const models = require('../models/index'); | ||
4 | + | ||
5 | +router.get('/', async (req, res, next) => { | ||
6 | + try { | ||
7 | + if (!req.isAuthenticated()) { | ||
8 | + return res.render('index', { | ||
9 | + title: '홈', | ||
10 | + user: null, | ||
11 | + posts: [], | ||
12 | + } | ||
13 | + ); | ||
14 | + } | ||
15 | + | ||
16 | + return res.render('index', { | ||
17 | + title: '홈', | ||
18 | + user: req.user, | ||
19 | + } | ||
20 | + ); | ||
21 | + } catch (e) { | ||
22 | + console.error(e); | ||
23 | + next(e); | ||
24 | + } | ||
25 | +}); | ||
26 | + | ||
27 | +module.exports = router; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
code/user_server/routes/middleware.js
0 → 100644
1 | +const isLoggedIn = (req, res, next) => { | ||
2 | + if (req.isAuthenticated()) { | ||
3 | + next(); | ||
4 | + } else { | ||
5 | + res.redirect('/'); | ||
6 | + } | ||
7 | +}; | ||
8 | + | ||
9 | +let isNotLoggedIn = (req, res, next) => { | ||
10 | + if (!req.isAuthenticated()) { | ||
11 | + next(); | ||
12 | + } else { | ||
13 | + res.redirect('/'); | ||
14 | + } | ||
15 | +}; | ||
16 | + | ||
17 | + | ||
18 | +module.exports = { | ||
19 | + isLoggedIn, | ||
20 | + isNotLoggedIn, | ||
21 | +}; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
code/user_server/routes/user.js
0 → 100644
1 | +const express = require('express'); | ||
2 | +const router = express.Router(); | ||
3 | +const bcrypt = require('bcrypt'); | ||
4 | +const passport = require('passport'); | ||
5 | +const {isLoggedIn, isNotLoggedIn} = require("./middleware"); | ||
6 | +const models = require('../models/index'); | ||
7 | + | ||
8 | +router.get('/loadMe', isLoggedIn, (req, res, next) => { | ||
9 | + console.log('loadMe요청옴', req.user); | ||
10 | + return res.json({user: req.user}); | ||
11 | +}); | ||
12 | + | ||
13 | +router.get('/signUp', isNotLoggedIn, (req, res, next) => { | ||
14 | + return res.render('SignUpComponent.vue', { | ||
15 | + title: '회원가입' | ||
16 | + }); | ||
17 | +}); | ||
18 | + | ||
19 | +router.post('/signUp', isNotLoggedIn, async (req, res, next) => { | ||
20 | + let {email, nickName, password} = req.body; | ||
21 | + try { | ||
22 | + let user = await models.User.findOne({ | ||
23 | + where: {email} | ||
24 | + }); | ||
25 | + if (user) { | ||
26 | + return res.json({user}); | ||
27 | + } | ||
28 | + | ||
29 | + const hashedPassword = await bcrypt.hash(password, 10); | ||
30 | + const signupComplete = await models.User.create({ | ||
31 | + email, nickName, hashedPassword | ||
32 | + }); | ||
33 | + | ||
34 | + user = await models.User.findOne({ | ||
35 | + where: {email}, | ||
36 | + attributes: ['id', 'email', 'nickName'] | ||
37 | + }); | ||
38 | + | ||
39 | + return req.login(user, (err) => { | ||
40 | + if (err) { | ||
41 | + console.error(err); | ||
42 | + return next(err); | ||
43 | + } | ||
44 | + return res.json({me: user}); | ||
45 | + }); | ||
46 | + } catch (e) { | ||
47 | + console.error(e); | ||
48 | + next(e); | ||
49 | + } | ||
50 | +}); | ||
51 | + | ||
52 | +router.post('/login', isNotLoggedIn, (req, res, next) => { | ||
53 | + passport.authenticate('local', {}, (err, user, info) => { | ||
54 | + if (err) { | ||
55 | + console.error(err); | ||
56 | + return next(err); | ||
57 | + } | ||
58 | + if (info) { | ||
59 | + console.error(info.message); | ||
60 | + return res.status(401).send(info.message); | ||
61 | + } | ||
62 | + req.login(user, (err) => { | ||
63 | + if (err) { | ||
64 | + console.error(err); | ||
65 | + return next(err); | ||
66 | + } | ||
67 | + ///////////////////////// req.session.returnURL | ||
68 | + // nuxt | ||
69 | + // return res.json({user: req.user}); | ||
70 | + return res.json({user: req.user}); | ||
71 | + }); | ||
72 | + })(req, res, next); | ||
73 | +}); | ||
74 | + | ||
75 | +router.get('/profile', isLoggedIn, (req, res, next) => { | ||
76 | + return res.render('profile', {title: '프로필', user: req.user}); | ||
77 | +}); | ||
78 | + | ||
79 | +router.post('/updateProfile', isLoggedIn, async (req, res, next) => { | ||
80 | + let {newNickName} = req.body; | ||
81 | + await models.User.update({ | ||
82 | + nickName: newNickName | ||
83 | + }, { | ||
84 | + where: {email: req.user.email} | ||
85 | + }); | ||
86 | + | ||
87 | + let user = await models.User.findOne({ | ||
88 | + where: {email: req.user.email} | ||
89 | + }); | ||
90 | + if (!user) { | ||
91 | + return res.redirect('/'); | ||
92 | + } | ||
93 | + | ||
94 | + return res.render('profile', { | ||
95 | + title: 'profile', | ||
96 | + user | ||
97 | + }) | ||
98 | +}); | ||
99 | + | ||
100 | +router.get('/deleteProfile', async (req, res, next) => { | ||
101 | + let email = {email: req.user.email}; | ||
102 | + let User = await models.User.destroy({ | ||
103 | + where: {email} | ||
104 | + }); | ||
105 | + return res.redirect('/'); | ||
106 | +}); | ||
107 | + | ||
108 | + | ||
109 | +router.get('/logout', (req, res, next) => { | ||
110 | + console.log('로그아웃 요청이 들어옴'); | ||
111 | + req.logout(); | ||
112 | + req.session.destroy(); | ||
113 | + return res.send(); | ||
114 | +}); | ||
115 | + | ||
116 | +module.exports = router; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
code/user_server/schemas/index.js
0 → 100644
1 | +const mongoose = require('mongoose'); | ||
2 | +const {MONGO_ID, MONGO_PASSWORD, NODE_ENV} = process.env; | ||
3 | +const MONGO_URL = `mongodb://localhost:27017/admin`; | ||
4 | +const connect = () => { | ||
5 | + if (process.env.NODE_VIEW !== 'production') { | ||
6 | + mongoose.set('debug', true); | ||
7 | + } | ||
8 | + mongoose.connect(MONGO_URL, { | ||
9 | + dbName: 'chat', | ||
10 | + useUnifiedTopology: true | ||
11 | + }, (err) => { | ||
12 | + if (err) { | ||
13 | + console.error('몽고디비 연결 에러', err); | ||
14 | + } else { | ||
15 | + console.log('몽고디비 연결 성공'); | ||
16 | + } | ||
17 | + }); | ||
18 | +}; | ||
19 | + | ||
20 | +module.exports = () => { | ||
21 | + connect(); | ||
22 | + mongoose.connection.on('error', (err) => { | ||
23 | + console.log('연결 종료'); | ||
24 | + }); | ||
25 | + mongoose.connection.on('disconnected', (err) => { | ||
26 | + console.error('연결이 끊어졌습니다. 재접속 시도중'); | ||
27 | + connect(); | ||
28 | + }); | ||
29 | +}; | ||
30 | +// 몽고디비는 데이터의 형식조건에서 자유롭다 | ||
31 | +// json객체 형태라면 무엇이든 저장이 가능하다 | ||
32 | +// 이러한 자유도에 제약을 걸고(형태에 제약) 안정성을 높이는 몽구스를 사용할 수 있다 | ||
33 | +// 몽고디비는 sql이 아닌 자바스크립트를 쓰기 때문에 노드와 궁합이 좋다 | ||
34 | +// 마이에스큐엘도 시퀄라이즈를 쓰면 자바스크립트로 제어할 수는 있다 | ||
35 | +// 몽고디비서버 실행 명령어: mongod --dbpath C:\Users\kimseoyoung\mongodb_data --auth | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
code/user_server/schemas/test.js
0 → 100644
1 | +<경희대학교 우정원 -> 영통역> | ||
2 | +{ | ||
3 | + "result": { //최상위 노드 | ||
4 | + "searchType": 0, //결과구분 0:도시내 | ||
5 | + "outTrafficCheck": 1, //환승 | ||
6 | + "busCount": 6, //버스6개 | ||
7 | + "subwayCount": 0,//지하철 0개 | ||
8 | + "subwayBusCount": 0, //버스&지하철 0 개 | ||
9 | + "pointDistance": 792, //출발지와 도착지의 직선거리 (필요없음) | ||
10 | + "startRadius": 700, //출발지 반경(필요없음) | ||
11 | + "endRadius": 700,//도착지 반경(필요없음) | ||
12 | + "path": [ //결과 리스트 확장 노드 | ||
13 | +{ | ||
14 | + "pathType": 2, //결과종류 2:버스 | ||
15 | + "info": { //요약정보 확장 노드 | ||
16 | + "trafficDistance": 934, //총이동거리 – 도보거리(필요없음) | ||
17 | + "totalWalk": 462, //총도보 이동거리 | ||
18 | + "totalTime": 14, //총소요시간 (분) | ||
19 | + "payment": 1050, //요금 (필요없음) | ||
20 | + "busTransitCount": 1, //버스환승 횟수 | ||
21 | + "subwayTransitCount": 0,//지하철 환승 횟수 | ||
22 | + "mapObj": "11235:1:13:16", //보간점 api를 호출하기 위한 파라미터값 | ||
23 | + "firstStartStation": "SK아파트", //최초 출발역 | ||
24 | + "lastEndStation": "동수원세무소.영통역2번출구", //최종 도착역 | ||
25 | + "totalStationCount": 3, //총 정류장 합 | ||
26 | + "busStationCount": 3,//버스 정류장 합 | ||
27 | + "subwayStationCount": 0, //총 지하철 정류장 합 | ||
28 | + "totalDistance": 1396, //총 거리 | ||
29 | + "totalWalkTime": -1// | ||
30 | +}, | ||
31 | + "subPath": [ //이동 교통수단 정보 확장 노드 | ||
32 | +{ | ||
33 | + "trafficType": 3, //도보 이동 | ||
34 | + "distance": 296, //도보 이동 거리 | ||
35 | + "sectionTime": 4 //도보 이동 소요시간-> 사용자 최적화 시키기 | ||
36 | +}, | ||
37 | +{ | ||
38 | + "trafficType": 2, | ||
39 | + "distance": 934, | ||
40 | + "sectionTime": 8, | ||
41 | + "stationCount": 3, | ||
42 | + "lane": [ | ||
43 | +{ | ||
44 | + "busNo": "55", | ||
45 | + "type": 3, | ||
46 | + "busID": 11235 | ||
47 | +} | ||
48 | + ], | ||
49 | + "startName": "SK아파트", | ||
50 | + "startX": 127.073755, | ||
51 | + "startY": 37.245495, | ||
52 | + "endName": "동수원세무소.영통역2번출구", | ||
53 | + "endX": 127.072699, | ||
54 | + "endY": 37.250704, | ||
55 | + "startID": 211483, | ||
56 | + "endID": 83255, | ||
57 | + "passStopList": { | ||
58 | + "stations": [ | ||
59 | +{ | ||
60 | + "index": 0, | ||
61 | + "stationID": 211483, | ||
62 | + "stationName": "SK아파트", | ||
63 | + "x": "127.073755", | ||
64 | + "y": "37.245495" | ||
65 | +}, | ||
66 | +{ | ||
67 | + "index": 1, | ||
68 | + "stationID": 184510, | ||
69 | + "stationName": "서그내", | ||
70 | + "x": "127.073454", | ||
71 | + "y": "37.247034" | ||
72 | +}, | ||
73 | +{ | ||
74 | + "index": 2, | ||
75 | + "stationID": 184509, | ||
76 | + "stationName": "영일중학교.수원출입국외국인청", | ||
77 | + "x": "127.075096", | ||
78 | + "y": "37.249227" | ||
79 | +}, | ||
80 | +{ | ||
81 | + "index": 3, | ||
82 | + "stationID": 83255, | ||
83 | + "stationName": "동수원세무소.영통역2번출구", | ||
84 | + "x": "127.072699", | ||
85 | + "y": "37.250704" | ||
86 | +} | ||
87 | + ] | ||
88 | +} | ||
89 | +}, | ||
90 | +{ | ||
91 | + "trafficType": 3, | ||
92 | + "distance": 166, | ||
93 | + "sectionTime": 2 | ||
94 | +} | ||
95 | + ] | ||
96 | +}, | ||
97 | +{ | ||
98 | + "pathType": 2, | ||
99 | + "info": { | ||
100 | + "trafficDistance": 610, | ||
101 | + "totalWalk": 363, | ||
102 | + "totalTime": 12, | ||
103 | + "payment": 1450, | ||
104 | + "busTransitCount": 1, | ||
105 | + "subwayTransitCount": 0, | ||
106 | + "mapObj": "9576:1:50:52", | ||
107 | + "firstStartStation": "경희대학교", | ||
108 | + "lastEndStation": "동수원세무소.영통역2번출구", | ||
109 | + "totalStationCount": 2, | ||
110 | + "busStationCount": 2, | ||
111 | + "subwayStationCount": 0, | ||
112 | + "totalDistance": 973, | ||
113 | + "totalWalkTime": -1 | ||
114 | +}, | ||
115 | + "subPath": [ | ||
116 | +{ | ||
117 | + "trafficType": 3, | ||
118 | + "distance": 197, | ||
119 | + "sectionTime": 3 | ||
120 | +}, | ||
121 | +{ | ||
122 | + "trafficType": 2, | ||
123 | + "distance": 610, | ||
124 | + "sectionTime": 7, | ||
125 | + "stationCount": 2, | ||
126 | + "lane": [ | ||
127 | +{ | ||
128 | + "busNo": "5", | ||
129 | + "type": 1, | ||
130 | + "busID": 9576 | ||
131 | +}, | ||
132 | +{ | ||
133 | + "busNo": "310", | ||
134 | + "type": 1, | ||
135 | + "busID": 9516 | ||
136 | +}, | ||
137 | +{ | ||
138 | + "busNo": "900", | ||
139 | + "type": 1, | ||
140 | + "busID": 9660 | ||
141 | +}, | ||
142 | +{ | ||
143 | + "busNo": "9", | ||
144 | + "type": 1, | ||
145 | + "busID": 9598 | ||
146 | +}, | ||
147 | +{ | ||
148 | + "busNo": "9-1", | ||
149 | + "type": 1, | ||
150 | + "busID": 9517 | ||
151 | +}, | ||
152 | +{ | ||
153 | + "busNo": "18", | ||
154 | + "type": 1, | ||
155 | + "busID": 9584 | ||
156 | +} | ||
157 | + ], | ||
158 | + "startName": "경희대학교", | ||
159 | + "startX": 127.077671, | ||
160 | + "startY": 37.247878, | ||
161 | + "endName": "동수원세무소.영통역2번출구", | ||
162 | + "endX": 127.072699, | ||
163 | + "endY": 37.250704, | ||
164 | + "startID": 184499, | ||
165 | + "endID": 83255, | ||
166 | + "passStopList": { | ||
167 | + "stations": [ | ||
168 | +{ | ||
169 | + "index": 0, | ||
170 | + "stationID": 184499, | ||
171 | + "stationName": "경희대학교", | ||
172 | + "x": "127.077671", | ||
173 | + "y": "37.247878" | ||
174 | +}, | ||
175 | +{ | ||
176 | + "index": 1, | ||
177 | + "stationID": 184509, | ||
178 | + "stationName": "영일중학교.수원출입국외국인청", | ||
179 | + "x": "127.075096", | ||
180 | + "y": "37.249227" | ||
181 | +}, | ||
182 | +{ | ||
183 | + "index": 2, | ||
184 | + "stationID": 83255, | ||
185 | + "stationName": "동수원세무소.영통역2번출구", | ||
186 | + "x": "127.072699", | ||
187 | + "y": "37.250704" | ||
188 | +} | ||
189 | + ] | ||
190 | +} | ||
191 | +}, | ||
192 | +{ | ||
193 | + "trafficType": 3, | ||
194 | + "distance": 166, | ||
195 | + "sectionTime": 2 | ||
196 | +} | ||
197 | + ] | ||
198 | +}, | ||
199 | +{ | ||
200 | + "pathType": 2, | ||
201 | + "info": { | ||
202 | + "trafficDistance": 1944, | ||
203 | + "totalWalk": 541, | ||
204 | + "totalTime": 16, | ||
205 | + "payment": 2800, | ||
206 | + "busTransitCount": 1, | ||
207 | + "subwayTransitCount": 0, | ||
208 | + "mapObj": "10501:1:3:6", | ||
209 | + "firstStartStation": "경희대학교", | ||
210 | + "lastEndStation": "영통역", | ||
211 | + "totalStationCount": 2, | ||
212 | + "busStationCount": 2, | ||
213 | + "subwayStationCount": 0, | ||
214 | + "totalDistance": 2485, | ||
215 | + "totalWalkTime": -1 | ||
216 | +}, | ||
217 | + "subPath": [ | ||
218 | +{ | ||
219 | + "trafficType": 3, | ||
220 | + "distance": 197, | ||
221 | + "sectionTime": 3 | ||
222 | +}, | ||
223 | +{ | ||
224 | + "trafficType": 2, | ||
225 | + "distance": 1944, | ||
226 | + "sectionTime": 8, | ||
227 | + "stationCount": 2, | ||
228 | + "lane": [ | ||
229 | +{ | ||
230 | + "busNo": "M5107", | ||
231 | + "type": 14, | ||
232 | + "busID": 10501 | ||
233 | +} | ||
234 | + ], | ||
235 | + "startName": "경희대학교", | ||
236 | + "startX": 127.077671, | ||
237 | + "startY": 37.247878, | ||
238 | + "endName": "영통역", | ||
239 | + "endX": 127.074057, | ||
240 | + "endY": 37.25395, | ||
241 | + "startID": 184499, | ||
242 | + "endID": 184643, | ||
243 | + "passStopList": { | ||
244 | + "stations": [ | ||
245 | +{ | ||
246 | + "index": 0, | ||
247 | + "stationID": 184499, | ||
248 | + "stationName": "경희대학교", | ||
249 | + "x": "127.077671", | ||
250 | + "y": "37.247878" | ||
251 | +}, | ||
252 | +{ | ||
253 | + "index": 1, | ||
254 | + "stationID": 88099, | ||
255 | + "stationName": "외서천삼거리", | ||
256 | + "x": "127.072291", | ||
257 | + "y": "37.247187" | ||
258 | +}, | ||
259 | +{ | ||
260 | + "index": 2, | ||
261 | + "stationID": 184641, | ||
262 | + "stationName": "살구골동아아파트", | ||
263 | + "x": "127.068013", | ||
264 | + "y": "37.247685" | ||
265 | +}, | ||
266 | +{ | ||
267 | + "index": 3, | ||
268 | + "stationID": 184643, | ||
269 | + "stationName": "영통역", | ||
270 | + "x": "127.074057", | ||
271 | + "y": "37.25395" | ||
272 | +} | ||
273 | + ] | ||
274 | +} | ||
275 | +}, | ||
276 | +{ | ||
277 | + "trafficType": 3, | ||
278 | + "distance": 344, | ||
279 | + "sectionTime": 5 | ||
280 | +} | ||
281 | + ] | ||
282 | +}, | ||
283 | +{ | ||
284 | + "pathType": 2, | ||
285 | + "info": { | ||
286 | + "trafficDistance": 454, | ||
287 | + "totalWalk": 705, | ||
288 | + "totalTime": 17, | ||
289 | + "payment": 2800, | ||
290 | + "busTransitCount": 1, | ||
291 | + "subwayTransitCount": 0, | ||
292 | + "mapObj": "10049:1:70:71", | ||
293 | + "firstStartStation": "경희대학교", | ||
294 | + "lastEndStation": "살구골.서광아파트", | ||
295 | + "totalStationCount": 1, | ||
296 | + "busStationCount": 1, | ||
297 | + "subwayStationCount": 0, | ||
298 | + "totalDistance": 1159, | ||
299 | + "totalWalkTime": -1 | ||
300 | +}, | ||
301 | + "subPath": [ | ||
302 | +{ | ||
303 | + "trafficType": 3, | ||
304 | + "distance": 197, | ||
305 | + "sectionTime": 3 | ||
306 | +}, | ||
307 | +{ | ||
308 | + "trafficType": 2, | ||
309 | + "distance": 454, | ||
310 | + "sectionTime": 6, | ||
311 | + "stationCount": 1, | ||
312 | + "lane": [ | ||
313 | +{ | ||
314 | + "busNo": "1550-1", | ||
315 | + "type": 4, | ||
316 | + "busID": 10049 | ||
317 | +} | ||
318 | + ], | ||
319 | + "startName": "경희대학교", | ||
320 | + "startX": 127.077671, | ||
321 | + "startY": 37.247878, | ||
322 | + "endName": "살구골.서광아파트", | ||
323 | + "endX": 127.07264, | ||
324 | + "endY": 37.24728, | ||
325 | + "startID": 184499, | ||
326 | + "endID": 184495, | ||
327 | + "passStopList": { | ||
328 | + "stations": [ | ||
329 | +{ | ||
330 | + "index": 0, | ||
331 | + "stationID": 184499, | ||
332 | + "stationName": "경희대학교", | ||
333 | + "x": "127.077671", | ||
334 | + "y": "37.247878" | ||
335 | +}, | ||
336 | +{ | ||
337 | + "index": 1, | ||
338 | + "stationID": 184495, | ||
339 | + "stationName": "살구골.서광아파트", | ||
340 | + "x": "127.07264", | ||
341 | + "y": "37.24728" | ||
342 | +} | ||
343 | + ] | ||
344 | +} | ||
345 | +}, | ||
346 | +{ | ||
347 | + "trafficType": 3, | ||
348 | + "distance": 508, | ||
349 | + "sectionTime": 8 | ||
350 | +} | ||
351 | + ] | ||
352 | +}, | ||
353 | +{ | ||
354 | + "pathType": 2, | ||
355 | + "info": { | ||
356 | + "trafficDistance": 1416, | ||
357 | + "totalWalk": 385, | ||
358 | + "totalTime": 14, | ||
359 | + "payment": 2800, | ||
360 | + "busTransitCount": 1, | ||
361 | + "subwayTransitCount": 0, | ||
362 | + "mapObj": "10052:1:4:7", | ||
363 | + "firstStartStation": "경희대학교", | ||
364 | + "lastEndStation": "살구골현대아파트.영통역4번출구", | ||
365 | + "totalStationCount": 3, | ||
366 | + "busStationCount": 3, | ||
367 | + "subwayStationCount": 0, | ||
368 | + "totalDistance": 1801, | ||
369 | + "totalWalkTime": -1 | ||
370 | +}, | ||
371 | + "subPath": [ | ||
372 | +{ | ||
373 | + "trafficType": 3, | ||
374 | + "distance": 197, | ||
375 | + "sectionTime": 3 | ||
376 | +}, | ||
377 | +{ | ||
378 | + "trafficType": 2, | ||
379 | + "distance": 1416, | ||
380 | + "sectionTime": 8, | ||
381 | + "stationCount": 3, | ||
382 | + "lane": [ | ||
383 | +{ | ||
384 | + "busNo": "1112", | ||
385 | + "type": 4, | ||
386 | + "busID": 10052 | ||
387 | +}, | ||
388 | +{ | ||
389 | + "busNo": "5100", | ||
390 | + "type": 4, | ||
391 | + "busID": 9564 | ||
392 | +} | ||
393 | + ], | ||
394 | + "startName": "경희대학교", | ||
395 | + "startX": 127.077671, | ||
396 | + "startY": 37.247878, | ||
397 | + "endName": "살구골현대아파트.영통역4번출구", | ||
398 | + "endX": 127.070462, | ||
399 | + "endY": 37.250191, | ||
400 | + "startID": 184499, | ||
401 | + "endID": 184689, | ||
402 | + "passStopList": { | ||
403 | + "stations": [ | ||
404 | +{ | ||
405 | + "index": 0, | ||
406 | + "stationID": 184499, | ||
407 | + "stationName": "경희대학교", | ||
408 | + "x": "127.077671", | ||
409 | + "y": "37.247878" | ||
410 | +}, | ||
411 | +{ | ||
412 | + "index": 1, | ||
413 | + "stationID": 184495, | ||
414 | + "stationName": "살구골.서광아파트", | ||
415 | + "x": "127.07264", | ||
416 | + "y": "37.24728" | ||
417 | +}, | ||
418 | +{ | ||
419 | + "index": 2, | ||
420 | + "stationID": 184641, | ||
421 | + "stationName": "살구골동아아파트", | ||
422 | + "x": "127.068013", | ||
423 | + "y": "37.247685" | ||
424 | +}, | ||
425 | +{ | ||
426 | + "index": 3, | ||
427 | + "stationID": 184689, | ||
428 | + "stationName": "살구골현대아파트.영통역4번출구", | ||
429 | + "x": "127.070462", | ||
430 | + "y": "37.250191" | ||
431 | +} | ||
432 | + ] | ||
433 | +} | ||
434 | +}, | ||
435 | +{ | ||
436 | + "trafficType": 3, | ||
437 | + "distance": 188, | ||
438 | + "sectionTime": 3 | ||
439 | +} | ||
440 | + ] | ||
441 | +}, | ||
442 | +{ | ||
443 | + "pathType": 2, | ||
444 | + "info": { | ||
445 | + "trafficDistance": 2784, | ||
446 | + "totalWalk": 337, | ||
447 | + "totalTime": 16, | ||
448 | + "payment": 2800, | ||
449 | + "busTransitCount": 1, | ||
450 | + "subwayTransitCount": 0, | ||
451 | + "mapObj": "9592:1:3:8", | ||
452 | + "firstStartStation": "경희대학교", | ||
453 | + "lastEndStation": "영통역6번출구.영덕고등학교", | ||
454 | + "totalStationCount": 5, | ||
455 | + "busStationCount": 5, | ||
456 | + "subwayStationCount": 0, | ||
457 | + "totalDistance": 3121, | ||
458 | + "totalWalkTime": -1 | ||
459 | +}, | ||
460 | + "subPath": [ | ||
461 | +{ | ||
462 | + "trafficType": 3, | ||
463 | + "distance": 197, | ||
464 | + "sectionTime": 3 | ||
465 | +}, | ||
466 | +{ | ||
467 | + "trafficType": 2, | ||
468 | + "distance": 2784, | ||
469 | + "sectionTime": 11, | ||
470 | + "stationCount": 5, | ||
471 | + "lane": [ | ||
472 | +{ | ||
473 | + "busNo": "7000", | ||
474 | + "type": 4, | ||
475 | + "busID": 9592 | ||
476 | +} | ||
477 | + ], | ||
478 | + "startName": "경희대학교", | ||
479 | + "startX": 127.077671, | ||
480 | + "startY": 37.247878, | ||
481 | + "endName": "영통역6번출구.영덕고등학교", | ||
482 | + "endX": 127.069841, | ||
483 | + "endY": 37.252142, | ||
484 | + "startID": 184499, | ||
485 | + "endID": 184508, | ||
486 | + "passStopList": { | ||
487 | + "stations": [ | ||
488 | +{ | ||
489 | + "index": 0, | ||
490 | + "stationID": 184499, | ||
491 | + "stationName": "경희대학교", | ||
492 | + "x": "127.077671", | ||
493 | + "y": "37.247878" | ||
494 | +}, | ||
495 | +{ | ||
496 | + "index": 1, | ||
497 | + "stationID": 184495, | ||
498 | + "stationName": "살구골.서광아파트", | ||
499 | + "x": "127.07264", | ||
500 | + "y": "37.24728" | ||
501 | +}, | ||
502 | +{ | ||
503 | + "index": 2, | ||
504 | + "stationID": 113705, | ||
505 | + "stationName": "영통롯데아파트", | ||
506 | + "x": "127.061285", | ||
507 | + "y": "37.246695" | ||
508 | +}, | ||
509 | +{ | ||
510 | + "index": 3, | ||
511 | + "stationID": 184503, | ||
512 | + "stationName": "벽적골태영아파트", | ||
513 | + "x": "127.062669", | ||
514 | + "y": "37.24958" | ||
515 | +}, | ||
516 | +{ | ||
517 | + "index": 4, | ||
518 | + "stationID": 184501, | ||
519 | + "stationName": "신나무실아파트", | ||
520 | + "x": "127.06605", | ||
521 | + "y": "37.252445" | ||
522 | +}, | ||
523 | +{ | ||
524 | + "index": 5, | ||
525 | + "stationID": 184508, | ||
526 | + "stationName": "영통역6번출구.영덕고등학교", | ||
527 | + "x": "127.069841", | ||
528 | + "y": "37.252142" | ||
529 | +} | ||
530 | + ] | ||
531 | +} | ||
532 | +}, | ||
533 | +{ | ||
534 | + "trafficType": 3, | ||
535 | + "distance": 140, | ||
536 | + "sectionTime": 2 | ||
537 | +} | ||
538 | + ] | ||
539 | +} | ||
540 | + ] | ||
541 | +} | ||
542 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
-
Please register or login to post a comment