Showing
61 changed files
with
2102 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
This diff is collapsed. Click to expand it.
-
Please register or login to post a comment