김성연

tjddus: add proto react_native, user_server

Showing 61 changed files with 2102 additions and 0 deletions
1 +# Default ignored files
2 +/workspace.xml
...\ 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
1 +<?xml version="1.0" encoding="UTF-8"?>
2 +<project version="4">
3 + <component name="JavaScriptSettings">
4 + <option name="languageLevel" value="JSX" />
5 + </component>
6 +</project>
...\ No newline at end of file ...\ No newline at end of file
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
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
1 +{
2 + "12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true,
3 + "40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true
4 +}
1 +node_modules/**/*
2 +.expo/*
3 +npm-debug.*
4 +*.jks
5 +*.p8
6 +*.p12
7 +*.key
8 +*.mobileprovision
9 +*.orig.*
10 +web-build/
11 +web-report/
12 +
13 +# macOS
14 +.DS_Store
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
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 +}
1 +module.exports = function(api) {
2 + api.cache(true);
3 + return {
4 + presets: ['babel-preset-expo'],
5 + };
6 +};
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
1 +import {Dimensions} from 'react-native';
2 +
3 +const {width, height} = Dimensions.get('screen');
4 +
5 +export default{
6 + width,
7 + height
8 +}
...\ No newline at end of file ...\ No newline at end of file
1 +const host = '172.20.10.3';
2 +
3 +export default host;
...\ No newline at end of file ...\ No newline at end of file
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
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 +}
1 +import {combineReducers} from "redux";
2 +import user from './user';
3 +// import post from './post';
4 +
5 +const rootReducer = combineReducers({
6 + user,
7 + // post
8 +});
9 +
10 +export default rootReducer;
...\ No newline at end of file ...\ No newline at end of file
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
1 +import {all, fork} from 'redux-saga/effects';
2 +
3 +import user from './user';
4 +// import post from './post';
5 +
6 +export default function* rootSaga(){
7 + yield all([
8 + fork(user),
9 + // fork(post)
10 + ])
11 +}
...\ No newline at end of file ...\ No newline at end of file
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
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
This diff could not be displayed because it is too large.
1 +PORT=4001
2 +COOKIE_SECRET=test
...\ No newline at end of file ...\ No newline at end of file
1 +# Default ignored files
2 +/workspace.xml
1 +<?xml version="1.0" encoding="UTF-8"?>
2 +<project version="4">
3 + <component name="JavaScriptSettings">
4 + <option name="languageLevel" value="ES6" />
5 + </component>
6 +</project>
...\ No newline at end of file ...\ No newline at end of file
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
1 +<?xml version="1.0" encoding="UTF-8"?>
2 +<project version="4">
3 + <component name="VcsDirectoryMappings">
4 + <mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
5 + </component>
6 +</project>
...\ No newline at end of file ...\ No newline at end of file
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
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
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 +}
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;
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
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 +}
This diff could not be displayed because it is too large.
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 +}
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 +};
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
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
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
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
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
This diff is collapsed. Click to expand it.