happiness6533

final

Showing 49 changed files with 2468 additions and 0 deletions
.expo/*
node_modules/**/*
app.json
npm-debug.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
*.orig.*
web-build/
# macOS
.DS_Store
.idea
.env
env.js
node_modules
package-lock.json
\ No newline at end of file
{
"12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true,
"40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true
}
> Why do I have a folder named ".expo" in my project?
The ".expo" folder is created when an Expo project is started using "expo start" command.
> What does the "packager-info.json" file contain?
The "packager-info.json" file contains port numbers and process PIDs that are used to serve the application to the mobile device/simulator.
> What does the "settings.json" file contain?
The "settings.json" file contains the server configuration that is used to serve the application manifest.
> Should I commit the ".expo" folder?
No, you should not share the ".expo" folder. It does not contain any information that is relevant for other developers working on the project, it is specific to your machine.
Upon project creation, the ".expo" folder is already added to your ".gitignore" file.
{
"packagerPort": null,
"packagerPid": null,
"expoServerPort": null,
"expoServerNgrokUrl": null,
"packagerNgrokUrl": null,
"ngrokPid": null
}
{
"hostType": "lan",
"lanType": "ip",
"dev": true,
"minify": false,
"urlRandomness": null,
"https": false
}
import React, {useState} from 'react';
import {View} from 'react-native';
import {AppLoading} from 'expo';
import * as SecureStore from 'expo-secure-store';
import AsyncStorage from '@react-native-community/async-storage';
import axios from "axios";
import env from './env';
import {StatusBar} from 'expo-status-bar';
import {Provider} from 'react-redux';
import store from "./store";
import {AuthProvider} from "./AuthContext";
import RootNavigation from "./navigations/RootNavigation";
export default function App() {
const [isAppLoading, setIsAppLoading] = useState(true);
const [user, setUser] = useState(null);
const loadAssets = async () => {
return new Promise(async (resolve, reject) => {
try {
let cookie;
if (SecureStore.isAvailableAsync()) {
cookie = await SecureStore.getItemAsync('cookie');
} else {
cookie = await AsyncStorage.getItem('cookie');
}
if (cookie) {
axios.defaults.headers.Cookie = cookie;
const res = await axios.get(`${env.HTTP_OR_HTTPS}://${env.HOST}:${env.PORT}/user/loadMe`, {
withCredentials: true
});
const {user} = res.data;
setUser(user);
}
return resolve();
} catch (e) {
console.error(e);
return reject();
}
})
};
const onFinish = async () => {
setIsAppLoading(false);
};
const onError = (err) => {
console.error(err);
};
return (
<>
{isAppLoading
?
<AppLoading
startAsync={loadAssets}
onFinish={onFinish}
onError={onError}/>
:
<View style={{
flex: 1,
flexDirection: 'column',
justifyContent: 'center'
}}>
<Provider store={store}>
<AuthProvider user={user}>
<RootNavigation/>
</AuthProvider>
</Provider>
<StatusBar/>
</View>
}
</>
);
}
\ No newline at end of file
import React, {createContext, useEffect} from 'react';
import {Text} from "react-native";
import {useDispatch, useSelector} from "react-redux";
import {loadMeReducer} from "./reducers/user";
import styled from "styled-components";
export const AuthContext = createContext({});
export const AuthProvider = (props) => {
const {children, user} = props;
const {info} = useSelector(state => state.info);
const dispatch = useDispatch();
useEffect(() => {
if (user) {
dispatch({
type: loadMeReducer,
data: {user}
});
}
}, [user]);
return (
<AuthContext.Provider>
{children}
{info
? <Info><Text
style={{
textAlign: 'center',
fontSize: 14,
color: '#bc324a'
}}>{info}</Text></Info>
: <></>
}
</AuthContext.Provider>
)
};
const Info = styled.TouchableOpacity`
position: absolute;
right: 10px;
bottom: 10px;
width: 300px;
height: 50px;
border-radius: 5px;
border: 1px solid #17174B;
backgroundColor: #ffffff;
justifyContent: center;
`;
\ No newline at end of file
module.exports = function(api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
};
};
import React, {useState, useCallback} from 'react';
import {Text} from 'react-native';
import {useDispatch} from "react-redux";
import {loginSaga} from "../../reducers/user";
import SquareButtonComponent from "../Base/SquareButtonComponent";
import {BUTTON_COLOR} from "../../constant";
import TextInputComponent from "../Base/TextInputComponent";
const LoginComponent = (props) => {
// 데이터
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
// 함수
const dispatch = useDispatch();
const onChangeEmail = useCallback(text => {
setEmail(text);
}, []);
const onChangePassword = useCallback((text) => {
setPassword(text);
}, []);
const login = useCallback(() => {
dispatch({
type: loginSaga,
data: {email, password}
});
}, [email, password]);
// 렌더링
return (
<>
<Text style={{
marginTop: 80,
marginBottom: 20,
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
color: '#373737'
}}>
로그인
</Text>
<TextInputComponent
onChangeText={onChangeEmail}
value={email}
secureTextEntry={false}
placeholder={'이메일을 쓰세요'}
rules={[
v => !!v || '이메일 입력은 필수입니다',
v => (v && (v.trim() !== '')) || '이메일 입력은 필수입니다',
v => (v && (v.length <= 100)) || '이메일의 길이는 100자 이하입니다',
v => (v && (/.+@.+/.test(v))) || '이메일의 형식이 올바르지 않습니다',
]}
style1={{
marginLeft: 20,
marginRight: 20,
marginTop: 0,
marginBottom: 0,
padding: 15,
}}
style2={{
marginLeft: 35,
marginRight: 35,
}}
/>
<TextInputComponent
onChangeText={onChangePassword}
value={password}
secureTextEntry={true}
placeholder={'비밀번호를 쓰세요'}
rules={[
v => !!v || '비밀번호 입력은 필수입니다',
v => (v && (v.trim() !== '')) || '비밀번호 입력은 필수입니다',
]}
style1={{
marginLeft: 20,
marginRight: 20,
marginTop: 0,
marginBottom: 0,
padding: 15,
}}
style2={{
marginLeft: 35,
marginRight: 35,
}}
/>
<SquareButtonComponent
onPress={login}
text={'로그인'}
style1={{
marginLeft: 20,
marginRight: 20,
marginTop: 10,
marginBottom: 10,
padding: 15,
borderRadius: 3,
backgroundColor: BUTTON_COLOR
}}
style2={{
fontSize: 16,
fontWeight: 'bold',
textAlign: 'center',
color: '#ffffff'
}}
/>
</>
)
};
export default LoginComponent;
\ No newline at end of file
import React, {useState, useCallback} from 'react';
import {Text} from 'react-native';
import {useDispatch} from 'react-redux';
import TextInputComponent from "../Base/TextInputComponent";
import SquareButtonComponent from "../Base/SquareButtonComponent";
import {BUTTON_COLOR} from "../../constant";
import {signUpSaga} from '../../reducers/user';
const SignUpComponent = (props) => {
// 데이터
const [email, setEmail] = useState('');
const [nickName, setNickName] = useState('');
const [password, setPassword] = useState('');
const [passwordCheck, setPasswordCheck] = useState('');
const [passwordCheckError, setPasswordCheckError] = useState(false);
// 함수
const dispatch = useDispatch();
const onChangeEmail = useCallback((text) => {
setEmail(text);
}, []);
const onChangeNickName = useCallback((text) => {
setNickName(text);
}, []);
const onChangePassword = useCallback((text) => {
setPassword(text);
}, []);
const onChangePasswordCheck = useCallback((text) => {
setPasswordCheck(text);
setPasswordCheckError(text !== password);
}, [password]);
const onSubmit = useCallback(() => {
if (!(email && nickName && password && passwordCheck && !passwordCheckError)) {
return;
}
dispatch({
type: signUpSaga,
data: {email, nickName, password}
});
}, [nickName, password, passwordCheck]);
return (
<>
<Text style={{
marginTop: 80,
marginBottom: 20,
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
color: '#373737'
}}>
회원가입
</Text>
<TextInputComponent
onChangeText={onChangeEmail}
value={email}
secureTextEntry={false}
placeholder={'이메일을 입력하세요'}
rules={[
v => !!v || '이메일 입력은 필수입니다',
v => (v && (v.trim() !== '')) || '이메일 입력은 필수입니다',
v => (v && (v.length <= 100)) || '이메일의 길이는 100자 이하입니다',
v => (v && (/.+@.+/.test(v))) || '이메일의 형식이 올바르지 않습니다',
]}
style1={{
marginLeft: 20,
marginRight: 20,
marginTop: 0,
marginBottom: 0,
padding: 15,
}}
style2={{
marginLeft: 35,
marginRight: 35,
}}
/>
<TextInputComponent
onChangeText={onChangeNickName}
value={nickName}
secureTextEntry={false}
placeholder={'닉네임을 입력하세요'}
rules={[
v => !!v || '닉네임 입력은 필수입니다',
v => (v && (v.trim() !== '')) || '닉네임 입력은 필수입니다',
v => (v && (v.length <= 10)) || '닉네임의 길이는 10자 이하입니다'
]}
style1={{
marginLeft: 20,
marginRight: 20,
marginTop: 0,
marginBottom: 0,
padding: 15,
}}
style2={{
marginLeft: 35,
marginRight: 35,
}}
/>
<TextInputComponent
onChangeText={onChangePassword}
value={password}
secureTextEntry={true}
placeholder={'비밀번호를 입력하세요'}
rules={[
v => !!v || '비밀번호 입력은 필수입니다',
v => (v && (v.trim() !== '')) || '비밀번호 입력은 필수입니다',
]}
style1={{
marginLeft: 20,
marginRight: 20,
marginTop: 0,
marginBottom: 0,
padding: 15,
}}
style2={{
marginLeft: 35,
marginRight: 35,
}}
/>
<TextInputComponent
onChangeText={onChangePasswordCheck}
value={passwordCheck}
secureTextEntry={true}
placeholder={'비밀번호를 다시 입력하세요'}
inputError={passwordCheckError ? '비밀번호가 일치하지 않습니다' : null}
style1={{
marginLeft: 20,
marginRight: 20,
marginTop: 0,
marginBottom: 0,
padding: 15,
}}
style2={{
marginLeft: 35,
marginRight: 35,
}}
/>
<SquareButtonComponent
onPress={onSubmit}
text={'회원가입'}
style1={{
marginLeft: 20,
marginRight: 20,
marginTop: 10,
marginBottom: 10,
padding: 15,
borderRadius: 3,
backgroundColor: BUTTON_COLOR
}}
style2={{
fontSize: 16,
fontWeight: 'bold',
textAlign: 'center',
color: '#ffffff'
}}
/>
</>
)
};
export default SignUpComponent;
\ No newline at end of file
import React from 'react';
import {TouchableOpacity, View, Text} from 'react-native';
const SquareButtonComponent = (props) => {
const {onPress, text, style1, style2} = props;
return (
<TouchableOpacity onPress={onPress}>
<View style={style1}>
<Text style={style2}>
{text}
</Text>
</View>
</TouchableOpacity>
)
};
export default SquareButtonComponent;
\ No newline at end of file
import React, {useState, useEffect} from 'react';
import {View, TextInput, Text, StyleSheet} from 'react-native';
const TextInputComponent = (props) => {
// 데이터
const {onChangeText, value, secureTextEntry, placeholder, rules, inputError, style1, style2} = props;
const [error, setError] = useState(null);
// 렌더링
useEffect(() => {
if (rules) {
let i = 0;
while (true) {
if (rules[i](value) !== true) {
setError(rules[i](value));
break;
} else {
if (i === rules.length - 1) {
setError(null);
break;
} else {
i++;
}
}
}
}
}, [value]);
return (
<>
<View style={{...styles.input, ...style1}}>
<TextInput style={{...styles.inputText}}
onChangeText={onChangeText}
value={value}
secureTextEntry={secureTextEntry}
placeholder={placeholder}/>
</View>
<Text style={{...styles.inputError, ...style2}}>{inputError ? inputError : error}</Text>
</>
)
};
const styles = StyleSheet.create({
input: {
borderRadius: 3,
backgroundColor: '#ebebeb',
},
inputText: {
fontSize: 16,
color: '#373737',
},
inputError: {
marginBottom: 5,
fontSize: 14,
color: '#9b0500',
}
});
export default TextInputComponent;
\ No newline at end of file
import React from "react";
import {View, Text, Image} from "react-native";
import {Callout, Marker} from "react-native-maps";
import {useNavigation} from '@react-navigation/native';
import SquareButtonComponent from "./Base/SquareButtonComponent";
import {BUTTON_COLOR} from "../constant";
const KickMarkerComponent = (props) => {
const navigation = useNavigation();
// 데이터
const {markerImage, serviceNumber, nameOfService, infoOfService, kickLocationMarker, index, minIndex, servicePrice, getPath} = props;
// 함수
const goToMyPage = () => {
navigation.navigate('이킥저킥');
};
// 렌더링
return (
<Marker coordinate={{
latitude: parseFloat(kickLocationMarker.coordinate.latitude),
longitude: parseFloat(kickLocationMarker.coordinate.longitude),
}}
onPress={() => getPath(serviceNumber, {
title: kickLocationMarker.title,
coordinate: {
latitude: parseFloat(kickLocationMarker.coordinate.latitude),
longitude: parseFloat(kickLocationMarker.coordinate.longitude),
}
})}
opacity={index === minIndex ? 1.0 : 0.6}
>
{serviceNumber === 0
?
<Image source={require('../assets/gcooter.png')} style={{height: 35, width: 35, borderRadius: 50}}/>
:
serviceNumber === 1
?
<Image source={require('../assets/flower_road.png')}
style={{height: 35, width: 35, borderRadius: 50}}/>
:
<Image source={require('../assets/ssing_ssing.png')}
style={{height: 35, width: 35, borderRadius: 50}}/>
}
<Callout tooltip={true}
style={{width: 200}} onPress={goToMyPage}>
<View style={{backgroundColor: '#ffffff', paddingLeft: 10, paddingRight: 10, paddingTop: 10, paddingBottom: 5, borderRadius: 5}}>
<View style={{
backgroundColor: '#ebebeb',
padding: 5,
marginBottom: 10,
borderRadius: 5
}}>
<Text style={{
fontSize: 16,
color: '#373737',
fontWeight: 'bold',
textAlign: 'center'
}}>
{index === minIndex ? '가까운' : ''} {nameOfService}
</Text>
{index === minIndex
?
<Text style={{
fontSize: 16,
color: '#373737',
fontWeight: 'bold',
textAlign: 'center'
}}>
예상비용 {servicePrice}
</Text>
:
<></>}
</View>
<Text style={{fontSize: 14, color: '#373737'}}>
남은 배터리 {kickLocationMarker.title}%
</Text>
<Text style={{fontSize: 14, color: '#373737'}}>
{infoOfService}
</Text>
<SquareButtonComponent
text={'대여하기'}
style1={{
marginLeft: 0,
marginRight: 0,
marginTop: 5,
marginBottom: 5,
padding: 5,
borderRadius: 3,
backgroundColor: BUTTON_COLOR
}}
style2={{
fontSize: 16,
fontWeight: 'bold',
textAlign: 'center',
color: '#ffffff'
}}
/>
</View>
</Callout>
</Marker>
);
};
export default KickMarkerComponent;
\ No newline at end of file
import React, {useState} from "react";
import {View, Text, Alert} from "react-native";
import {CheckBox} from "native-base";
import SquareButtonComponent from "../Base/SquareButtonComponent";
import {BUTTON_COLOR} from "../../constant";
import {resetLaneReducer} from "../../reducers/location";
import {useDispatch} from "react-redux";
const Modal1Component = (props) => {
// 데이터
const {setModalNumber, setShowButtons, setModalVisible, serviceStates, setServiceStates, selectKickServices} = props;
const [showDetail, setShowDetail] = useState(true);
// 함수
const dispatch = useDispatch();
const onPressCheckBox = (indexOfService) => {
if (indexOfService === 1) {
setServiceStates([...serviceStates.slice(0, indexOfService), !serviceStates[indexOfService], ...serviceStates.slice(indexOfService + 1)]);
} else {
setServiceStates([...serviceStates.slice(0, indexOfService), serviceStates[indexOfService], ...serviceStates.slice(indexOfService + 1)]);
}
};
const onShowDetail = () => {
setShowDetail(!showDetail);
};
const onDecideServices = () => {
if (serviceStates.findIndex(v => v) === -1) {
return Alert.alert('최소 하나 이상의 서비스를 선택하세요');
}
selectKickServices(serviceStates);
setModalNumber(0);
setShowButtons(true);
setModalVisible(false);
dispatch({type: resetLaneReducer});
};
// 렌더링
return (
<View style={{'flex': 1, width: 270}}>
<View style={{'flex': 1, 'flexDirection': 'column', marginBottom: 15}}>
<Text style={{fontSize: 24, fontWeight: 'bold', textAlign: 'center', color: '#373737'}}>
서비스 선택
</Text>
</View>
<View style={{'flex': 3, 'flexDirection': 'column'}}>
<View style={{'flex': 1, 'flexDirection': 'row'}}>
<Text style={{fontSize: 16, fontWeight: 'bold', color: '#373737'}}>지쿠터(현재 미지원)</Text>
<CheckBox checked={serviceStates[0]} color="#4844ff"
onPress={() => onPressCheckBox(0)}/>
</View>
<View style={{'flex': 3, 'flexDirection': 'row'}}>
{!showDetail
? <Text style={{fontSize: 16, color: '#373737'}}>#저렴한 가격 #최다 보유</Text>
: <Text style={{
fontSize: 15, color: '#373737'
}}>{`기본 1분 300 / 1분당 150\n최대 20km/h\n00-05시 새벽 할증 요금제`}</Text>}
</View>
</View>
<View style={{'flex': 3, 'flexDirection': 'column'}}>
<View style={{'flex': 1, 'flexDirection': 'row'}}>
<Text style={{fontSize: 16, fontWeight: 'bold', color: '#373737'}}>플라워 로드</Text>
<CheckBox checked={serviceStates[1]} color="#4844ff"
onPress={() => onPressCheckBox(1)}/>
</View>
<View style={{'flex': 3, 'flexDirection': 'row'}}>
{!showDetail
? <Text style={{fontSize: 16, color: '#373737'}}>#빠른 속도</Text>
: <Text style={{
fontSize: 15, color: '#373737'
}}>{`기본 5분 1000/1분당 100\n최대 25km/h\n21시 이후 최대속도 18km 제한`}</Text>}
</View>
</View>
<View style={{'flex': 3, 'flexDirection': 'column'}}>
<View style={{'flex': 1, 'flexDirection': 'row'}}>
<Text style={{fontSize: 16, fontWeight: 'bold', color: '#373737'}}>씽씽(현재 미지원)</Text>
<CheckBox checked={serviceStates[2]} color="#4844ff"
onPress={() => onPressCheckBox(2)}/>
</View>
<View style={{'flex': 3, 'flexDirection': 'row'}}>
{!showDetail
? <Text style={{fontSize: 16, color: '#373737'}}>#최신 기기</Text>
: <Text style={{
fontSize: 15, color: '#373737'
}}>{`평일/심야/주말 요금 상이\n최대 25km/h\n9월 말까지 50% 할인 행사중`}</Text>}
</View>
</View>
<SquareButtonComponent
onPress={onShowDetail}
text={!showDetail ? '상세 정보 보기' : '간단 정보 보기'}
style1={{
marginLeft: 0,
marginRight: 0,
marginTop: 10,
marginBottom: 0,
padding: 10,
borderRadius: 3,
backgroundColor: BUTTON_COLOR
}}
style2={{
fontSize: 16,
fontWeight: 'bold',
textAlign: 'center',
color: '#ffffff'
}}
/>
<SquareButtonComponent
onPress={onDecideServices}
text={'서비스 결정하기'}
style1={{
marginLeft: 0,
marginRight: 0,
marginTop: 10,
marginBottom: 0,
padding: 10,
borderRadius: 3,
backgroundColor: BUTTON_COLOR
}}
style2={{
fontSize: 16,
fontWeight: 'bold',
textAlign: 'center',
color: '#ffffff'
}}
/>
</View>
);
};
export default Modal1Component;
\ No newline at end of file
import React from 'react';
import {View, Text} from 'react-native';
import {useDispatch} from "react-redux";
import {setStartLocationSaga, setEndLocationSaga} from "../../reducers/location";
import TextInputComponent from "../Base/TextInputComponent";
import SquareButtonComponent from "../Base/SquareButtonComponent";
import {BUTTON_COLOR} from "../../constant";
const Modal2Component = (props) => {
// 데이터
const {closeModal, setShowButtons, setModalVisible, serviceStates, setModalNumber, startTextLocation, endTextLocation, setStartTextLocation, setEndTextLocation} = props;
// 함수
const dispatch = useDispatch();
const onChangeStartLocation = (text) => {
setStartTextLocation(text);
};
const onChangeFinishLocation = (text) => {
setEndTextLocation(text);
};
const setLocation = async () => {
try {
if (startTextLocation && endTextLocation) {
await dispatch({
type: setStartLocationSaga,
data: {
startTextLocation,
}
});
await dispatch({
type: setEndLocationSaga,
data: {
endTextLocation
}
});
if (serviceStates.findIndex(serviceState => serviceState) === -1) {
setModalNumber(2);
setShowButtons(false);
setModalVisible(true);
} else {
setModalNumber(0);
setShowButtons(true);
setModalVisible(false);
}
}
} catch (e) {
console.error(e);
}
};
const close = () => {
closeModal();
};
// 렌더링
return (
<View style={{'flex': 1, width: 270}}>
<View style={{'flex': 1, 'flexDirection': 'column', marginBottom: 15}}>
<Text style={{fontSize: 24, fontWeight: 'bold', textAlign: 'center', color: '#373737'}}>
출발 도착 설정
</Text>
</View>
<View style={{'flex': 9, 'flexDirection': 'column'}}>
<TextInputComponent
onChangeText={onChangeStartLocation}
value={startTextLocation}
secureTextEntry={false}
placeholder={'출발 장소를 입력하세요'}
style1={{
marginLeft: 0,
marginRight: 0,
marginTop: 0,
marginBottom: 0,
padding: 15,
}}
style2={{
marginLeft: 15,
marginRight: 15,
}}
/>
<TextInputComponent
onChangeText={onChangeFinishLocation}
value={endTextLocation}
secureTextEntry={false}
placeholder={'도착 장소를 입력하세요'}
style1={{
marginLeft: 0,
marginRight: 0,
marginTop: 0,
marginBottom: 0,
padding: 15,
}}
style2={{
marginLeft: 15,
marginRight: 15,
}}
/>
<SquareButtonComponent
onPress={setLocation}
text={'출발지와 도착지 결정'}
style1={{
marginLeft: 0,
marginRight: 0,
marginTop: 10,
marginBottom: 0,
padding: 10,
borderRadius: 3,
backgroundColor: BUTTON_COLOR
}}
style2={{
fontSize: 16,
fontWeight: 'bold',
textAlign: 'center',
color: '#ffffff'
}}
/>
<SquareButtonComponent
onPress={close}
text={'닫기'}
style1={{
marginLeft: 0,
marginRight: 0,
marginTop: 10,
marginBottom: 0,
padding: 10,
borderRadius: 3,
backgroundColor: BUTTON_COLOR
}}
style2={{
fontSize: 16,
fontWeight: 'bold',
textAlign: 'center',
color: '#ffffff'
}}
/>
</View>
</View>
)
};
export default Modal2Component;
\ No newline at end of file
import React from 'react';
import {View, Text} from 'react-native';
import {useDispatch} from "react-redux";
import {setStartLocationSaga} from "../../reducers/location";
import TextInputComponent from "../Base/TextInputComponent";
import SquareButtonComponent from "../Base/SquareButtonComponent";
import {BUTTON_COLOR} from "../../constant";
const Modal3Component = (props) => {
// 데이터
const {closeModal, setModalNumber, setShowButtons, setModalVisible, startTextLocation, setStartTextLocation} = props;
// 함수
const dispatch = useDispatch();
const onChangeStartLocation = (text) => {
setStartTextLocation(text);
};
const setLocation = async () => {
try {
if (startTextLocation) {
await dispatch({
type: setStartLocationSaga,
data: {
startTextLocation,
}
});
closeModal();
}
} catch (e) {
console.error(e);
}
};
const close = () => {
closeModal();
};
// 렌더링
return (
<View style={{'flex': 1, width: 270}}>
<View style={{'flex': 1, 'flexDirection': 'column', marginBottom: 50}}>
<Text style={{fontSize: 24, fontWeight: 'bold', textAlign: 'center', color: '#373737'}}>
장소 검색{'\n'}(대학교를 검색해보세요)
</Text>
</View>
<View style={{'flex': 9, 'flexDirection': 'column'}}>
<TextInputComponent
onChangeText={onChangeStartLocation}
value={startTextLocation}
secureTextEntry={false}
placeholder={'검색할 장소를 입력하세요'}
style1={{
marginLeft: 0,
marginRight: 0,
marginTop: 0,
marginBottom: 0,
padding: 15,
}}
style2={{
marginLeft: 15,
marginRight: 15,
}}
/>
<SquareButtonComponent
onPress={setLocation}
text={'결정'}
style1={{
marginLeft: 0,
marginRight: 0,
marginTop: 10,
marginBottom: 0,
padding: 10,
borderRadius: 3,
backgroundColor: BUTTON_COLOR
}}
style2={{
fontSize: 16,
fontWeight: 'bold',
textAlign: 'center',
color: '#ffffff'
}}
/>
<SquareButtonComponent
text={'현재 위치로 바로가기(현재 미지원)'}
style1={{
marginLeft: 0,
marginRight: 0,
marginTop: 10,
marginBottom: 0,
padding: 10,
borderRadius: 3,
backgroundColor: BUTTON_COLOR
}}
style2={{
fontSize: 16,
fontWeight: 'bold',
textAlign: 'center',
color: '#ffffff'
}}
/>
<SquareButtonComponent
onPress={close}
text={'닫기'}
style1={{
marginLeft: 0,
marginRight: 0,
marginTop: 10,
marginBottom: 0,
padding: 10,
borderRadius: 3,
backgroundColor: BUTTON_COLOR
}}
style2={{
fontSize: 16,
fontWeight: 'bold',
textAlign: 'center',
color: '#ffffff'
}}
/>
</View>
</View>
)
};
export default Modal3Component;
\ No newline at end of file
import {Dimensions} from 'react-native';
export const BUTTON_COLOR = '#5051ff';
export const WIDTH_OF_DEVICE = Dimensions.get('screen').width;
export const HEIGHT_OF_DEVICE = Dimensions.get('screen').height;
\ No newline at end of file
[1024/142550.860:ERROR:directory_reader_win.cc(43)] FindFirstFile: 지정된 경로를 찾을 수 없습니다. (0x3)
[1024/161824.551:ERROR:directory_reader_win.cc(43)] FindFirstFile: 지정된 경로를 찾을 수 없습니다. (0x3)
[1024/172714.074:ERROR:directory_reader_win.cc(43)] FindFirstFile: 지정된 경로를 찾을 수 없습니다. (0x3)
[1025/190018.321:ERROR:directory_reader_win.cc(43)] FindFirstFile: 지정된 경로를 찾을 수 없습니다. (0x3)
[1130/200856.582:ERROR:directory_reader_win.cc(43)] FindFirstFile: 지정된 경로를 찾을 수 없습니다. (0x3)
[1201/231225.210:ERROR:directory_reader_win.cc(43)] FindFirstFile: 지정된 경로를 찾을 수 없습니다. (0x3)
import React from "react";
import {NavigationContainer} from "@react-navigation/native";
import {createStackNavigator} from "@react-navigation/stack";
import Map from "../screens/Map";
import Auth from "../screens/Auth";
import MyPage from "../screens/MyPage";
import {useSelector} from "react-redux";
const Stack = createStackNavigator();
const RootNavigation = () => {
const {me} = useSelector(state => state.user);
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name='지도'
component={Map}
options={{
headerShown: false
}}/>
<Stack.Screen name='이킥저킥'
component={me ? MyPage : Auth}/>
</Stack.Navigator>
</NavigationContainer>
);
};
export default RootNavigation;
\ No newline at end of file
{
"main": "node_modules/expo/AppEntry.js",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"eject": "expo eject"
},
"dependencies": {
"@expo/vector-icons": "^10.0.0",
"@react-native-community/async-storage": "~1.12.0",
"@react-native-community/masked-view": "^0.1.10",
"@react-navigation/native": "^5.1.4",
"@react-navigation/stack": "^5.2.9",
"axios": "^0.19.2",
"expo": "^39.0.0",
"expo-document-picker": "^8.4.1",
"expo-location": "~9.0.0",
"expo-secure-store": "~9.2.0",
"expo-status-bar": "~1.0.2",
"native-base": "^2.13.14",
"react": "16.13.1",
"react-dom": "16.13.1",
"react-native": "https://github.com/expo/react-native/archive/sdk-39.0.0.tar.gz",
"react-native-gesture-handler": "^1.8.0",
"react-native-maps": "0.27.1",
"react-native-reanimated": "~1.13.0",
"react-native-safe-area-context": "3.1.4",
"react-native-screens": "~2.15.0",
"react-native-tab-view": "^2.14.0",
"react-native-web": "~0.13.7",
"react-redux": "^7.2.0",
"redux": "^4.0.5",
"redux-saga": "^1.1.3",
"saga": "^4.0.0-alpha",
"styled-components": "^5.0.1"
},
"devDependencies": {
"@babel/core": "~7.9.0"
},
"private": true
}
import {combineReducers} from 'redux';
import user from './user';
import location from './location';
import info from './info';
const rootReducer = combineReducers({
user,
location,
info,
});
export default rootReducer;
\ No newline at end of file
export const setInfoReducer = 'setInfoReducer';
export const resetInfoReducer = 'resetInfoReducer';
export const initialState = {
info: null,
};
export default (state = initialState, action) => {
switch (action.type) {
case setInfoReducer: {
const {info} = action.data;
return {
...state,
info,
}
}
case resetInfoReducer: {
return {
...state,
info: null,
};
}
default: {
return {
...state,
};
}
}
};
\ No newline at end of file
export const setStartLocationSaga = 'setStartLocationSaga';
export const setEndLocationSaga = 'setEndLocationSaga';
export const setKickLocationsSaga = 'setKickLocationsSaga';
export const setKickRouteSaga = 'setKickRouteSaga';
export const setStartLocationReducer = 'setStartLocationReducer';
export const setEndLocationReducer = 'setEndLocationReducer';
export const setKickLocationsReducer = 'setKickLocationsReducer';
export const setKickRouteReducer = 'setKickRouteReducer';
export const resetLaneReducer = 'resetLaneReducer';
export const initialState = {
startLocation: null,
endLocation: null,
kickLocationsArray: [[], [], []],
lane: [],
distance: null,
times: null,
};
export default (state = initialState, action) => {
switch (action.type) {
case setStartLocationSaga: {
return {
...state,
};
}
case setEndLocationSaga: {
return {
...state,
};
}
case setKickLocationsSaga: {
return {
...state,
};
}
case setKickRouteSaga: {
return {
...state,
}
}
case setStartLocationReducer: {
const {location} = action.data;
return {
...state,
startLocation: location
}
}
case setEndLocationReducer: {
const {location} = action.data;
return {
...state,
endLocation: location
}
}
case setKickLocationsReducer: {
let {kickLocationsArray} = action.data;
return {
...state,
kickLocationsArray,
}
}
case setKickRouteReducer: {
let {lane, distance, time} = action.data;
return {
...state,
lane, distance, time,
}
}
case resetLaneReducer: {
return {
...state,
lane: [],
}
}
default: {
return {
...state,
};
}
}
};
\ No newline at end of file
export const signUpSaga = 'signUpSaga';
export const loginSaga = 'loginSaga';
export const loadMeSaga = 'loadMeSaga';
export const updateUserInfoSaga = 'updateUserInfoSaga';
export const logoutSaga = 'logoutSaga';
export const loginReducer = 'loginReducer';
export const loadMeReducer = 'loadMeReducer';
export const updateUserInfoReducer = 'updateUserInfoReducer';
export const logoutReducer = 'logoutReducer';
export const initialState = {
me: null,
};
export default (state = initialState, action) => {
switch (action.type) {
case signUpSaga: {
return {
...state,
};
}
case loginSaga: {
return {
...state,
};
}
case loadMeSaga: {
return {
...state,
};
}
case updateUserInfoSaga: {
return {
...state,
};
}
case logoutSaga: {
return {
...state,
};
}
case loginReducer: {
const {user} = action.data;
return {
...state,
me: user,
};
}
case loadMeReducer: {
const {user} = action.data;
return {
...state,
me: user,
};
}
case updateUserInfoReducer: {
const {user} = action.data;
return {
...state,
me: user,
}
}
case logoutReducer: {
return {
...state,
me: null,
}
}
default: {
return {
...state,
};
}
}
};
\ No newline at end of file
import {all, fork} from 'redux-saga/effects';
import user from './user';
import location from './location';
const rootSaga = function* () {
yield all([
fork(user),
fork(location),
]);
};
export default rootSaga;
\ No newline at end of file
import {all, call, delay, fork, put, takeLatest} from 'redux-saga/effects';
import axios from 'axios';
import {
setStartLocationSaga,
setEndLocationSaga,
setKickLocationsSaga,
setKickRouteSaga,
setStartLocationReducer,
setEndLocationReducer,
setKickLocationsReducer,
setKickRouteReducer,
} from "../reducers/location";
import {
setInfoReducer,
resetInfoReducer,
} from '../reducers/info';
import env from '../env';
function setStartLocationAPI(data) {
const {startTextLocation} = data;
return axios.get(`${env.HTTP_OR_HTTPS}://${env.HOST}:${env.PORT}/api/findLocation/${encodeURIComponent(startTextLocation)}`);
}
function* setStartLocation(action) {
try {
const res = yield call(setStartLocationAPI, action.data);
const {location} = res.data;
yield put({
type: setStartLocationReducer,
data: {location}
})
} catch (e) {
console.error(e);
if (e.response && e.response.data.info) {
const {info} = e.response.data;
yield put({
type: setInfoReducer,
data: {info}
});
yield delay(3000);
yield put({
type: resetInfoReducer,
})
}
}
}
function* watchSetStartLocation() {
yield takeLatest(setStartLocationSaga, setStartLocation);
}
function setEndLocationAPI(data) {
const {endTextLocation} = data;
return axios.get(`${env.HTTP_OR_HTTPS}://${env.HOST}:${env.PORT}/api/findLocation/${encodeURIComponent(endTextLocation)}`);
}
function* setEndLocation(action) {
try {
const res = yield call(setEndLocationAPI, action.data);
const {location} = res.data;
yield put({
type: setEndLocationReducer,
data: {location}
});
} catch (e) {
console.error(e);
if (e.response && e.response.data.info) {
const {info} = e.response.data;
yield put({
type: setInfoReducer,
data: {info}
});
yield delay(3000);
yield put({
type: resetInfoReducer,
})
}
}
}
function* watchSetEndLocation() {
yield takeLatest(setEndLocationSaga, setEndLocation);
}
function setKickLocationsAPI(data) {
const {startLocation, distance, serviceStates} = data;
return axios.post(`${env.HTTP_OR_HTTPS}://${env.HOST}:${env.PORT}/api/findKicks`, {
startLocation, distance, serviceStates
});
}
function* setKickLocations(action) {
try {
const res = yield call(setKickLocationsAPI, action.data);
const {kickLocationsArray} = res.data;
yield put({
type: setKickLocationsReducer,
data: {kickLocationsArray}
})
} catch (e) {
console.error(e);
if (e.response && e.response.data.info) {
const {info} = e.response.data;
yield put({
type: setInfoReducer,
data: {info}
});
yield delay(3000);
yield put({
type: resetInfoReducer,
})
}
}
}
function* watchSetKickLocations() {
yield takeLatest(setKickLocationsSaga, setKickLocations);
}
function setKickRouteAPI(data) {
const {startLocation, endLocation, personalVelocity} = data;
return axios.post(`${env.HTTP_OR_HTTPS}://${env.HOST}:${env.PORT}/api/setKickRoute`, {
startLocation,
endLocation,
personalVelocity,
});
}
function* setKickRoute(action) {
try {
const res = yield call(setKickRouteAPI, action.data);
const {lane, distance, time} = res.data;
yield put({
type: setKickRouteReducer,
data: {lane, distance, time}
})
} catch (e) {
console.error(e);
if (e.response && e.response.data.info) {
const {info} = e.response.data;
yield put({
type: setInfoReducer,
data: {info}
});
yield delay(3000);
yield put({
type: resetInfoReducer,
})
}
}
}
function* watchSetKickRoute() {
yield takeLatest(setKickRouteSaga, setKickRoute);
}
export default function* locationSaga() {
yield all([
fork(watchSetStartLocation),
fork(watchSetEndLocation),
fork(watchSetKickLocations),
fork(watchSetKickRoute),
]);
};
\ No newline at end of file
import {all, fork, takeEvery, call, put, delay} from 'redux-saga/effects';
import axios from 'axios';
import * as SecureStore from 'expo-secure-store';
import AsyncStorage from '@react-native-community/async-storage';
import {
signUpSaga,
loginSaga,
loadMeSaga,
updateUserInfoSaga,
logoutSaga,
loginReducer,
loadMeReducer,
updateUserInfoReducer,
logoutReducer,
} from '../reducers/user';
import {
setInfoReducer,
resetInfoReducer,
} from '../reducers/info';
import env from "../env";
function signUpAPI(data) {
const {email, nickName, password} = data;
return axios.post(`${env.HTTP_OR_HTTPS}://${env.HOST}:${env.PORT}/user/signUp`, {
email, nickName, password
}, {
withCredentials: true
});
}
function* signUp(action) {
try {
const res = yield call(signUpAPI, action.data);
const {info} = res.data;
yield put({
type: setInfoReducer,
data: {info}
});
yield delay(5000);
yield put({
type: resetInfoReducer,
})
} catch (e) {
console.error(e);
if (e.response && e.response.data.info) {
const {info} = e.response.data;
yield put({
type: setInfoReducer,
data: {info}
});
yield delay(3000);
yield put({
type: resetInfoReducer,
})
}
}
}
function* signUpSagaWatch() {
yield takeEvery(signUpSaga, signUp);
}
async function loginAPI(data) {
const {email, password} = data;
const res = await axios.post(`${env.HTTP_OR_HTTPS}://${env.HOST}:${env.PORT}/user/login`, {
email, password,
}, {
withCredentials: true
});
const {user} = res.data;
const cookie = res.headers['set-cookie'][0];
if (SecureStore.isAvailableAsync()) {
await SecureStore.setItemAsync('cookie', cookie);
} else {
await AsyncStorage.setItem('cookie', cookie);
}
axios.defaults.headers.Cookie = cookie;
return {user};
}
function* login(action) {
try {
const {user} = yield call(loginAPI, action.data);
yield put({
type: loginReducer,
data: {user}
});
} catch (e) {
console.error(e);
if (e.response && e.response.data.info) {
const {info} = e.response.data;
yield put({
type: setInfoReducer,
data: {info}
});
yield delay(3000);
yield put({
type: resetInfoReducer,
})
}
}
}
function* loginSagaWatch() {
yield takeEvery(loginSaga, login);
}
function loadMeAPI(data) {
return axios.get(`${env.HTTP_OR_HTTPS}://${env.HOST}:${env.HOST}/user/loadMe`, {
withCredentials: true
});
}
function* loadMe(action) {
try {
const res = yield call(loadMeAPI, action.data);
const {user} = res.data;
yield put({
type: loadMeReducer,
data: {user}
});
} catch (e) {
console.error(e);
if (e.response && e.response.data.info) {
const {info} = e.response.data;
yield put({
type: setInfoReducer,
data: {info}
});
yield delay(3000);
yield put({
type: resetInfoReducer,
})
}
}
}
function* loadMeSagaWatch() {
yield takeEvery(loadMeSaga, loadMe);
}
function logoutAPI(data) {
return axios.get(`${env.HTTP_OR_HTTPS}://${env.HOST}:${env.PORT}/user/logout`, {
withCredentials: true
});
}
function* logout(action) {
try {
yield call(logoutAPI, action.data);
yield put({
type: logoutReducer,
});
} catch (e) {
console.error(e);
if (e.response && e.response.data.info) {
const {info} = e.response.data;
yield put({
type: setInfoReducer,
data: {info}
});
yield delay(3000);
yield put({
type: resetInfoReducer,
})
}
}
}
function* logoutSagaWatch() {
yield takeEvery(logoutSaga, logout);
}
function updateUserInfoAPI(data) {
const {nickName} = data;
return axios.post(`${env.HTTP_OR_HTTPS}://${env.HOST}:${env.PORT}/user/updateUserInfo`, {
nickName,
}, {
withCredentials: true
});
}
function* updateUserInfo(action) {
try {
const res = yield call(updateUserInfoAPI, action.data);
const {user} = res.data;
yield put({
type: updateUserInfoReducer,
data: {user}
});
} catch (e) {
console.error(e);
if (e.response && e.response.data.info) {
const {info} = e.response.data;
yield put({
type: setInfoReducer,
data: {info}
});
yield delay(3000);
yield put({
type: resetInfoReducer,
})
}
}
}
function* updateUserInfoSagaWatch() {
yield takeEvery(updateUserInfoSaga, updateUserInfo);
}
export default function* userSaga() {
yield all([
fork(signUpSagaWatch),
fork(loginSagaWatch),
fork(loadMeSagaWatch),
fork(updateUserInfoSagaWatch),
fork(logoutSagaWatch),
]);
}
\ No newline at end of file
import React, {useState, useEffect} from 'react';
import {ScrollView} from 'react-native';
import LoginComponent from "../components/Auth/LoginComponent";
import SignUpComponent from "../components/Auth/SignUpComponent";
import {useSelector} from "react-redux";
import {BUTTON_COLOR} from "../constant";
import SquareButtonComponent from "../components/Base/SquareButtonComponent";
const Auth = (props) => {
// 데이터
const {navigation} = props;
const [tryLogin, setTryLogin] = useState(true);
const {me} = useSelector(state => state.user);
// 함수
const changeTryLogin = () => {
setTryLogin(prev => !prev)
};
// 렌더링
useEffect(() => {
if (me) {
navigation.navigate('마이페이지');
}
}, [me]);
return (
<ScrollView>
{
tryLogin
?
<>
<LoginComponent/>
<SquareButtonComponent
onPress={changeTryLogin}
text={'회원가입'}
style1={{
marginLeft: 20,
marginRight: 20,
marginTop: 10,
marginBottom: 10,
padding: 15,
borderRadius: 3,
backgroundColor: BUTTON_COLOR
}}
style2={{
fontSize: 16,
fontWeight: 'bold',
textAlign: 'center',
color: '#ffffff'
}}
/>
</>
:
<>
<SignUpComponent/>
<SquareButtonComponent
onPress={changeTryLogin}
text={'로그인'}
style1={{
marginLeft: 20,
marginRight: 20,
marginTop: 10,
marginBottom: 10,
padding: 15,
borderRadius: 3,
backgroundColor: BUTTON_COLOR
}}
style2={{
fontSize: 16,
fontWeight: 'bold',
textAlign: 'center',
color: '#ffffff'
}}
/>
</>
}
</ScrollView>
)
};
export default Auth;
\ No newline at end of file
This diff is collapsed. Click to expand it.
import React, {useEffect, useState, useCallback} from 'react';
import {ScrollView, Text, Alert} from 'react-native';
import {useDispatch, useSelector} from "react-redux";
import {BUTTON_COLOR} from "../constant";
import SquareButtonComponent from "../components/Base/SquareButtonComponent";
import {logoutSaga, updateUserInfoSaga} from "../reducers/user";
import TextInputComponent from "../components/Base/TextInputComponent";
const MyPage = (props) => {
// 데이터
const {navigation} = props;
const {me} = useSelector(state => state.user);
const [nickName, setNickName] = useState(me.nickName);
// 함수
const dispatch = useDispatch();
const goToPay = () => {
Alert.alert('테스트 앱은 결제 기능 미지원');
};
const onChangeNickName = useCallback((text) => {
setNickName(text);
}, []);
const updateUserInfo = () => {
dispatch({type: updateUserInfoSaga, data: {nickName}});
};
const logout = () => {
dispatch({type: logoutSaga});
};
// 렌더링
useEffect(() => {
if (!me) {
navigation.navigate('지도');
}
}, [me]);
return (
<ScrollView>
<Text style={{
marginTop: 80,
marginBottom: 20,
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
color: '#373737'
}}>
닉네임 {me.nickName}
</Text>
<TextInputComponent
onChangeText={onChangeNickName}
value={nickName}
secureTextEntry={false}
placeholder={'닉네임을 업데이트하세요'}
rules={[
v => !!v || '닉네임 입력은 필수입니다',
v => (v && (v.trim() !== '')) || '닉네임 입력은 필수입니다',
v => (v && (v.length <= 10)) || '닉네임의 길이는 10자 이하입니다'
]}
style1={{
marginLeft: 20,
marginRight: 20,
marginTop: 10,
marginBottom: 10,
padding: 15,
}}
style2={{
marginLeft: 35,
marginRight: 35,
}}
/>
<SquareButtonComponent
onPress={updateUserInfo}
text={'닉네임 업데이트'}
style1={{
marginLeft: 20,
marginRight: 20,
marginTop: 10,
marginBottom: 10,
padding: 15,
borderRadius: 3,
backgroundColor: BUTTON_COLOR
}}
style2={{
fontSize: 16,
fontWeight: 'bold',
textAlign: 'center',
color: '#ffffff'
}}
/>
<SquareButtonComponent
onPress={logout}
text={'로그아웃'}
style1={{
marginLeft: 20,
marginRight: 20,
marginTop: 10,
marginBottom: 10,
padding: 15,
borderRadius: 3,
backgroundColor: BUTTON_COLOR
}}
style2={{
fontSize: 16,
fontWeight: 'bold',
textAlign: 'center',
color: '#ffffff'
}}
/>
<SquareButtonComponent
onPress={goToPay}
text={'대여 및 결제 페이지'}
style1={{
marginLeft: 20,
marginRight: 20,
marginTop: 10,
marginBottom: 10,
padding: 15,
borderRadius: 3,
backgroundColor: BUTTON_COLOR
}}
style2={{
fontSize: 16,
fontWeight: 'bold',
textAlign: 'center',
color: '#ffffff'
}}
/>
</ScrollView>
)
};
export default MyPage;
\ No newline at end of file
import createSagaMiddleware from "redux-saga";
import {compose, applyMiddleware, createStore} from "redux";
import rootReducer from "./reducers/index";
import rootSaga from "./sagas/index";
const sagaMiddleware = createSagaMiddleware();
const middlewares = [sagaMiddleware];
const enhancer = compose(applyMiddleware(...middlewares));
const store = createStore(rootReducer, enhancer);
store.sagaTask = sagaMiddleware.run(rootSaga);
export default store;
\ No newline at end of file
(async () => {
const models = require('./models/index');
models.sequelize.sync({force: false});
})();
const express = require('express');
const path = require('path');
const morgan = require('morgan');
const helmet = require('helmet');
const hpp = require('hpp');
const cors = require('cors');
const cookieParser = require('cookie-parser');
const expressSession = require('express-session');
const httpErrors = require('http-errors');
const MongoStore = require('connect-mongo')(expressSession);
const sessionMiddleware = expressSession({
resave: false,
saveUninitialized: false,
secret: process.env.COOKIE_SECRET,
cookie: {
path: '/',
httpOnly: false,
secure: false,
},
name: 'KickCookie',
store: new MongoStore({
url: process.env.NODE_ENV === 'development'
? `mongodb://${process.env.MONGO_DEV_USER}:${process.env.MONGO_DEV_PASSWORD}@${process.env.MONGO_DEV_HOST}:${process.env.MONGO_DEV_PORT}/admin`
: `mongodb://${process.env.MONGO_USER}:${process.env.MONGO_PASSWORD}@${process.env.MONGO_HOST}:${process.env.MONGO_PORT}/admin`,
dbName: process.env.NODE_ENV === 'development' ? process.env.MONGO_SESSION_DEV_DB_NAME : process.env.MONGO_SESSION_DB_NAME,
}),
});
const passport = require('passport');
const passportIndex = require('./passport/index');
passportIndex(passport);
const app = express();
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
if (process.env.NODE_ENV === 'development') {
app.use(morgan('dev'));
} else {
app.use(morgan('combined'));
app.use(helmet());
app.use(hpp());
}
app.use(cors({
origin: '*',
credentials: true
}));
app.use(express.json());
app.use(express.urlencoded({extended: false}));
app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(sessionMiddleware);
app.use(passport.initialize());
app.use(passport.session());
app.use('/user', require('./routes/user'));
app.use('/api', require('./routes/api'));
app.use(function (req, res, next) {
next(httpErrors(404));
});
app.use(function (err, req, res, next) {
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
\ No newline at end of file
#!/usr/bin/env node
const dotenv = require('dotenv');
dotenv.config();
const debug = require('debug')('Kick_user_and_api_server');
const app = require('../app');
const http = require('http');
const normalizePort = (val) => {
const port = parseInt(val, 10);
if (isNaN(port)) {
return val;
}
if (port >= 0) {
return port;
}
return false;
};
const onError = (error) => {
if (error.syscall !== 'listen') {
throw error;
}
const bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
};
const onListening = () => {
const addr = server.address();
const bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
};
const port = normalizePort(process.env.PORT);
app.set('port', port);
const server = http.createServer(app);
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
\ No newline at end of file
const config = {
'development': {
'username': process.env.MYSQL_DEV_USER,
'password': process.env.MYSQL_DEV_USER_PASSWORD,
'host': process.env.MYSQL_DEV_HOST,
'port': process.env.MYSQL_DEV_PORT,
'database': process.env.KICK_DEV_DB_NAME,
'dialect': 'mysql',
'operatorsAliases': false
},
'test': {
'username': process.env.MYSQL_DEV_USER,
'password': process.env.MYSQL_DEV_USER_PASSWORD,
'host': process.env.MYSQL_DEV_HOST,
'port': process.env.MYSQL_DEV_PORT,
'database': process.env.KICK_DEV_DB_NAME,
'dialect': 'mysql',
'operatorsAliases': false
},
'production': {
'username': process.env.MYSQL_USER,
'password': process.env.MYSQL_USER_PASSWORD,
'host': process.env.MYSQL_HOST,
'port': process.env.MYSQL_PORT,
'database': process.env.KICK_DB_NAME,
'dialect': 'mysql',
'operatorsAliases': false,
'logging': false
}
};
module.exports = config;
\ No newline at end of file
'use strict';
const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || 'development';
const config = require(__dirname + './../config/config.js')[env];
const models = {};
let sequelize;
if (config.use_env_variable) {
sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
sequelize = new Sequelize(config.database, config.username, config.password, config);
}
fs
.readdirSync(__dirname)
.filter(file => {
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
})
.forEach(file => {
const model = sequelize['import'](path.join(__dirname, file));
models[model.name] = model;
});
models.User = require('./user')(sequelize, Sequelize);
Object.keys(models).forEach(modelName => {
if (models[modelName].associate) {
models[modelName].associate(models);
}
});
models.sequelize = sequelize;
models.Sequelize = Sequelize;
module.exports = models;
\ No newline at end of file
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
email: {
type: DataTypes.STRING(100),
unique: true,
allowNull: false,
},
hashedPassword: {
type: DataTypes.TEXT,
unique: false,
allowNull: false,
},
nickName: {
type: DataTypes.STRING(10),
unique: true,
allowNull: false,
},
}, {
timestamps: true,
paranoid: false,
underscored: false,
charset: 'utf8mb4',
collate: 'utf8mb4_general_ci',
});
return User;
};
\ No newline at end of file
{
"name": "kick_user_and_api_server",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "cross-env NODE_ENV=development nodemon ./bin/www",
"start": "cross-env NODE_ENV=production node ./bin/www"
},
"dependencies": {
"axios": "^0.19.2",
"bcrypt": "^3.0.7",
"connect-mongo": "^3.2.0",
"cookie-parser": "~1.4.4",
"cookie-signature": "^1.1.0",
"cors": "^2.8.5",
"cross-env": "^7.0.2",
"debug": "~2.6.9",
"ejs": "~2.6.1",
"dotenv": "^8.2.0",
"express": "~4.16.1",
"express-session": "^1.17.0",
"helmet": "^4.1.0",
"hpp": "^0.2.3",
"http-errors": "~1.6.3",
"mongoose": "^5.9.4",
"morgan": "~1.9.1",
"mysql2": "^1.7.0",
"passport": "^0.4.1",
"passport-local": "^1.0.0",
"sequelize": "^5.21.3",
"sequelize-cli": "^5.5.1"
},
"devDependencies": {
"nodemon": "^2.0.4"
}
}
const passport = require('passport');
const localStrategy = require('./localStrategy');
const models = require('../models/index');
module.exports = () => {
passport.serializeUser((user, done) => {
return done(null, user.email);
});
passport.deserializeUser(async (email, done) => {
try {
const user = await models.User.findOne({
where: {email},
attributes: ['id', 'nickName']
});
return done(null, user);
} catch (e) {
console.error(e);
return done(e);
}
});
localStrategy(passport);
};
\ No newline at end of file
const LocalStrategy = require('passport-local').Strategy;
const bcrypt = require('bcrypt');
const models = require('../models/index');
module.exports = (passport) => {
passport.use(new LocalStrategy({
usernameField: 'email',
passwordField: 'password'
}, async (email, password, done) => {
try {
let user = await models.User.findOne({where: {email}});
if (!user) {
return done(null, false, {info: '이메일 또는 비밀번호가 일치하지 않습니다'});
}
const result = await bcrypt.compare(password, user.hashedPassword);
if (!result) {
return done(null, false, {info: '이메일 또는 비밀번호가 일치하지 않습니다'});
}
user = await models.User.findOne({
where: {email},
attributes: ['id', 'email', 'nickName']
});
return done(null, user);
} catch (e) {
console.error(e);
return done(e);
}
})
);
};
\ No newline at end of file
const express = require('express');
const router = express.Router();
const axios = require('axios');
router.get('/findLocation/:textLocation', async (req, res, next) => {
try {
let {textLocation} = req.params;
textLocation = decodeURIComponent(textLocation);
const result = await axios.get(`https://api.vworld.kr/req/address?service=address&request=getcoord&version=1.0&crs=epsg:4326&address=${encodeURIComponent(textLocation)}&refine=true&simple=false&format=json&type=road&key=${process.env.MAP_API_KEY}`);
const {response} = result.data;
const location = {
title: textLocation,
coordinate: {
longitude: parseFloat(response.result.point.x),
latitude: parseFloat(response.result.point.y),
}
};
return res.json({location});
} catch (e) {
console.error(e);
next(e);
}
});
router.post('/findKicks', async (req, res, next) => {
try {
const {startLocation, distance, serviceStates} = req.body;
let kickLocationsArray = [[], [], []];
if (serviceStates[1]) {
const result = await axios.get(`https://ext.flowerroad.ai/external/scooter/scooter_list?lat=${startLocation.coordinate.latitude}&lon=${startLocation.coordinate.longitude}&distance=${distance}`, {
headers: {
accept: 'application/json',
'x-api-key': process.env.KICK_API_KEY
}
});
const {data} = result.data;
if (data.length !== 0) {
kickLocationsArray[1] = data.map(kickLocation => {
return {
title: kickLocation.battery,
coordinate: {
longitude: parseFloat(kickLocation.lon),
latitude: parseFloat(kickLocation.lat),
},
}
});
}
}
return res.json({kickLocationsArray});
} catch (e) {
console.error(e);
next(e);
}
});
router.post('/setKickRoute', async (req, res, next) => {
try {
const {startLocation, endLocation, personalVelocity} = req.body;
const startLocationX = startLocation.coordinate.longitude;
const startLocationY = startLocation.coordinate.latitude;
const endLocationX = endLocation.coordinate.longitude;
const endLocationY = endLocation.coordinate.latitude;
const result = await axios.post('https://apis.openapi.sk.com/tmap/routes/pedestrian?version=1&format=json', {
"startX": startLocationX,
"startY": startLocationY,
"endX": endLocationX,
"endY": endLocationY,
"speed": personalVelocity,
"startName": "출발지",
"endName": "도착지",
"angle": 1,
"endPoiId": 334852,
"reqCoordType": "WGS84GEO",
"searchOption": 0,
"resCoordType": "WGS84GEO",
}, {
headers: {
accept: 'application/json',
'content-type': 'application/json; charset=UTF-8',
appKey: process.env.PATH_API_KEY,
}
});
const {features} = result.data;
const drawInfoArr = [];
for (let i = 0; i < features.length; i++) {
const geometry = features[i].geometry;
if (geometry.type === 'Point') {
drawInfoArr.push(geometry.coordinates);
} else {
geometry.coordinates.forEach(coordinate => drawInfoArr.push(coordinate));
}
}
const lane = drawInfoArr.map(v => {
return {longitude: v[0], latitude: v[1]}
});
const distance = features[0].properties.totalDistance;
const time = features[0].properties.totalTime / 60;
return res.json({lane, distance, time});
} catch (e) {
console.error(e);
next(e);
}
});
module.exports = router;
\ No newline at end of file
const isNotLoggedIn = (req, res, next) => {
if (!req.isAuthenticated()) {
next();
} else {
req.logout();
req.session.destroy();
return res.status(401).json({info: '네트워크에 일시적인 장애가 발생해서 로그아웃 되었습니다. 다시 시도해주세요'});
}
};
const isLoggedIn = (req, res, next) => {
if (req.isAuthenticated()) {
next();
} else {
return res.status(401).json({});
}
};
module.exports = {
isNotLoggedIn,
isLoggedIn,
};
\ No newline at end of file
const express = require('express');
const router = express.Router();
const bcrypt = require('bcrypt');
const passport = require('passport');
const {isLoggedIn, isNotLoggedIn} = require("./middleware");
const models = require('../models/index');
router.post('/signUp', isNotLoggedIn, async (req, res, next) => {
try {
const {email, password, nickName} = req.body;
let user = await models.User.findOne({where: {email}});
if (user) {
return res.status(401).json({info: '중복된 이메일입니다'});
}
user = await models.User.findOne({where: {nickName}});
if (user) {
return res.status(401).json({info: '중복된 닉네임입니다'});
}
const hashedPassword = await bcrypt.hash(password, 10);
await models.User.create({
email, hashedPassword, nickName
});
return res.json({info: '회원가입 완료. 로그인 하세요'});
} catch (e) {
console.error(e);
next(e);
}
});
router.post('/login', isNotLoggedIn, (req, res, next) => {
passport.authenticate('local', {}, (err, user, info) => {
if (err) {
console.error(err);
return next(err);
}
if (info) {
return res.status(401).json({info});
}
req.login(user, (err) => {
if (err) {
console.error(err);
return next(err);
}
return res.json({user});
});
})(req, res, next);
});
router.get('/loadMe', isLoggedIn, (req, res, next) => {
try {
return res.json({user: req.user});
} catch (e) {
console.error(e);
next(e);
}
});
router.post('/updateUserInfo', isLoggedIn, async (req, res, next) => {
try {
const {nickName} = req.body;
await models.User.update({
nickName
}, {
where: {id: req.user.id}
});
const user = await models.User.findOne({where: {id: req.user.id}});
return res.json({user});
} catch (e) {
console.error(e);
next(e);
}
});
router.get('/logout', isLoggedIn, (req, res, next) => {
try {
req.logout();
req.session.destroy();
return res.json({});
} catch (e) {
console.error(e);
next(e);
}
});
module.exports = router;
\ No newline at end of file
<h1><%= message %></h1>
<h2><%= error.status %></h2>
<pre><%= error.stack %></pre>
\ No newline at end of file