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