최원석
Committed by GitHub

Merge pull request #11 from goesnow/goesnow

Goesnow
...@@ -7,6 +7,9 @@ crawler/chromedriver ...@@ -7,6 +7,9 @@ crawler/chromedriver
7 crawler/chromedriver.exe 7 crawler/chromedriver.exe
8 **/*.exe 8 **/*.exe
9 package-lock.json 9 package-lock.json
10 +__pycache__/
11 +**/__pycache__/
12 +*.pyc
10 13
11 **/build/ 14 **/build/
12 public 15 public
......
...@@ -2,12 +2,32 @@ ...@@ -2,12 +2,32 @@
2 2
3 Lookup ID, and Compare! 3 Lookup ID, and Compare!
4 4
5 +<span style="font-size:24px;">SPA</span> with <span style="font-size:16px;">Vanilla Typescript</span>
6 +
7 +and <span style="font-size:24px;">Page Transitions</span> with <span style="font-size:16px;">Scss</span>
8 +
9 +and <span style="font-size:24px;">Instagram Crawler</span> with <span style="font-size:16px;">Python</span>
10 +
11 +---
12 +
13 +### Using ...
14 +
15 +- Typescript
16 +- Webpack
17 +- Scss
18 +- Flask
19 +
5 ## Execute server 20 ## Execute server
21 +listing for __*localhost:8080*__
6 ```shell 22 ```shell
7 python -m pipenv shell 23 python -m pipenv shell
8 python server.py 24 python server.py
9 ``` 25 ```
10 26
27 +or
28 +
29 +> just run server with **Pycharm**
30 +
11 ## Execute only frontend 31 ## Execute only frontend
12 32
13 [link](https://github.com/1Seok2/check-your-instagram/tree/master/app) 33 [link](https://github.com/1Seok2/check-your-instagram/tree/master/app)
......
No preview for this file type
...@@ -26,6 +26,8 @@ ...@@ -26,6 +26,8 @@
26 "file-loader": "^6.2.0", 26 "file-loader": "^6.2.0",
27 "gts": "^3.1.0", 27 "gts": "^3.1.0",
28 "html-webpack-plugin": "^5.3.0", 28 "html-webpack-plugin": "^5.3.0",
29 + "node-sass": "^5.0.0",
30 + "sass-loader": "^11.0.1",
29 "style-loader": "^2.0.0", 31 "style-loader": "^2.0.0",
30 "ts-loader": "^8.0.17", 32 "ts-loader": "^8.0.17",
31 "typescript": "^4.0.3", 33 "typescript": "^4.0.3",
......
...@@ -6,19 +6,20 @@ ...@@ -6,19 +6,20 @@
6 * 컴포넌트 제작 후 반환 6 * 컴포넌트 제작 후 반환
7 **/ 7 **/
8 8
9 -import Header from "./views/header/Header"; 9 +import Header from "./views/header";
10 -import Body from "./views/body/Body"; 10 +import Body from "./views/body";
11 -import Footer from "./views/footer/Footer"; 11 +import Footer from "./views/footer";
12 +import './assets/style/App.scss';
13 +import './assets/style/PageTransition.scss';
12 14
13 const App = (pathname : string) : string => { 15 const App = (pathname : string) : string => {
14 history.pushState('','', pathname); 16 history.pushState('','', pathname);
15 17
16 return ` 18 return `
17 - <div> 19 + <div class="container">
18 ${Header()} 20 ${Header()}
19 ${Body(pathname)} 21 ${Body(pathname)}
20 ${Footer()} 22 ${Footer()}
21 - <a href="/wonseog" id="nav-button" data-link>wonseok!!</a>
22 </div> 23 </div>
23 `; 24 `;
24 } 25 }
......
1 -body{
2 - background-color: beige;
3 -}
...\ No newline at end of file ...\ No newline at end of file
1 +body {
2 + margin: 0;
3 + padding: 0;
4 +}
5 +
6 +.container {
7 + display: flex;
8 + justify-content: center;
9 + align-items: center;
10 + flex-direction: column;
11 + position: absolute;
12 + top: 50%;
13 + left: 50%;
14 + transform: translate(-50%, -50%);
15 + min-width: 250px;
16 + min-height: 500px;
17 + width: 100%;
18 + height: 100%;
19 +}
...\ No newline at end of file ...\ No newline at end of file
1 +.body {
2 + border: 1px solid gray;
3 + width: 100%;
4 + flex-grow: 1;
5 + display: flex;
6 + justify-content: center;
7 + align-items: center;
8 + flex-direction: column;
9 +}
...\ No newline at end of file ...\ No newline at end of file
1 +.footer {
2 + height: 50px;
3 + background-color: #afafaf;
4 + display: flex;
5 + justify-content: center;
6 + align-items: center;
7 + font-size: .8rem;
8 + width: 100%;
9 + color: #fafafa;
10 +}
...\ No newline at end of file ...\ No newline at end of file
1 +.header {
2 + height: 70px;
3 + display: flex;
4 + justify-content: center;
5 + align-items: center;
6 + font-size: 1.8rem;
7 + width: 100%;
8 +}
...\ No newline at end of file ...\ No newline at end of file
1 +@keyframes page-initial-bottom {
2 + 0%{
3 + opacity: 1;
4 + background-color: white;
5 + }
6 + 100%{
7 + opacity: 0;
8 + background-color: white;
9 + }
10 +}
11 +
12 +@keyframes page-blink-bottom {
13 + 0%{
14 + opacity: 0;
15 + top: 50%;
16 + left: 50%
17 + }
18 + 40%{
19 + opacity: 1;
20 + left: 50%;
21 + }
22 + 50%{
23 + left: 50%;
24 + }
25 + 100%{
26 + opacity: 1;
27 + left: -70%;
28 + }
29 +}
30 +
31 +@keyframes page-top-left {
32 + 0%{
33 + left: 50%;
34 + top: 170%;
35 + }
36 + 50%{
37 + top: 50%;
38 + left: 50%;
39 + }
40 + 100%{
41 + top: 50%;
42 + left: -70%;
43 + }
44 +}
45 +
46 +.page-transition {
47 + width : 120%;
48 + height: 120%;
49 + position: fixed;
50 + left: 50%;
51 + top: 50%;
52 + transform: translate(-50%,-50%);
53 + background-color: #353535;
54 + z-index: 1;
55 + display: none;
56 +
57 + &.transition-0 {
58 + animation: ease-in-out 1.2s page-initial-bottom;
59 + }
60 +
61 + &.transition-1 {
62 + animation: ease-in-out 2.4s page-blink-bottom;
63 + }
64 +
65 + &.transition-2 {
66 + animation: ease-in-out 2.4s page-top-left;
67 + }
68 +}
...\ No newline at end of file ...\ No newline at end of file
1 +/**
2 + * @author : wonseog
3 + * @date : 2021/03/09
4 + * @description : 페이지 이동 효과 주는 함수
5 +**/
6 +
7 +let $div : HTMLDivElement | null;
8 +
9 +const pageTransitionClassList = [
10 + "transition-0",
11 + "transition-1",
12 + "transition-2"
13 +]
14 +
15 +const getDivs = () : HTMLDivElement | null => document.querySelector('.page-transition');
16 +
17 +const turnOnAndOff = (idx : number)=>{
18 + $div && (()=>{
19 + $div.classList.toggle(pageTransitionClassList[idx]);
20 + $div.style.display = 'block';
21 +
22 + setTimeout(()=>{
23 + $div && (()=>{
24 + $div.style.display = 'none';
25 + $div.classList.toggle(pageTransitionClassList[idx]);
26 + })()
27 + },idx === 0 ? 1000 : 2400);
28 + })()
29 +}
30 +
31 +export const initialTrantition = () => {
32 + $div = $div || getDivs();
33 + $div && turnOnAndOff(0);
34 +}
35 +
36 +export const randomTransition = () => {
37 + $div = $div || getDivs();
38 + const randomIndex = Math.floor(Math.random()*(pageTransitionClassList.length - 1) + 1);
39 +
40 + $div && turnOnAndOff(randomIndex);
41 +}
...\ No newline at end of file ...\ No newline at end of file
1 -export const BASE_URL = 'http://localhost:5000/'; 1 +export const BASE_URL = 'http://localhost:8080/';
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
4 <base href="/" /> 4 <base href="/" />
5 <meta charset="UTF-8"> 5 <meta charset="UTF-8">
6 <title>home</title> 6 <title>home</title>
7 + <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 </head> 8 </head>
8 <body> 9 <body>
9 <div id="App"></div> 10 <div id="App"></div>
......
...@@ -3,20 +3,47 @@ ...@@ -3,20 +3,47 @@
3 * @date : 2021/03/08 3 * @date : 2021/03/08
4 * @description : 현재 pathname을 파악 후 App으로 전달 4 * @description : 현재 pathname을 파악 후 App으로 전달
5 **/ 5 **/
6 +
6 import App from './App'; 7 import App from './App';
7 import {BASE_URL} from './config/url'; 8 import {BASE_URL} from './config/url';
9 +import {initialTrantition, randomTransition} from "./components/pageTransition";
10 +import {setState} from "./state/state";
8 11
9 window.addEventListener('DOMContentLoaded', () => { 12 window.addEventListener('DOMContentLoaded', () => {
13 + /* add div for page transitions */
14 + (()=> {
15 + const $div = document.createElement('div');
16 + $div.className = "page-transition";
17 + document.body.appendChild($div);
18 +
19 + initialTrantition();
20 + })();
21 +
10 const $App = document.querySelector('#App'); 22 const $App = document.querySelector('#App');
11 const pathname = window.location.pathname.split('/')[1]; 23 const pathname = window.location.pathname.split('/')[1];
12 24
25 + /* change url */
26 + window.addEventListener('popstate', ()=>{
27 + $App && ($App.innerHTML = App(pathname));
28 + });
29 +
30 + /* initial render */
31 + $App && ($App.innerHTML = App(pathname));
32 +
13 document.body.addEventListener('click', async (e) => { 33 document.body.addEventListener('click', async (e) => {
14 e.stopPropagation(); 34 e.stopPropagation();
35 +
36 + /* add navigation click event */
15 if((e.target as HTMLAnchorElement).matches("[data-link]")){ 37 if((e.target as HTMLAnchorElement).matches("[data-link]")){
16 - const href = (e.target as HTMLAnchorElement).href.split(BASE_URL)[1]; 38 + const href : string= (e.target as HTMLAnchorElement).href.split(BASE_URL)[1];
17 e.preventDefault(); 39 e.preventDefault();
18 - $App && ($App.innerHTML = App(href)); 40 + setTimeout(()=>{
19 - } else if((e.target as HTMLAnchorElement).id === 'update-button') { 41 + $App && ($App.innerHTML = App(href));
42 + },1200);
43 + randomTransition();
44 + }
45 + /* add POST event for button */
46 + else if((e.target as HTMLAnchorElement).id === 'update-button') {
20 let result: any= null; 47 let result: any= null;
21 const insta_id = (document.querySelector('#id-input') as HTMLInputElement).value; 48 const insta_id = (document.querySelector('#id-input') as HTMLInputElement).value;
22 49
...@@ -26,16 +53,15 @@ window.addEventListener('DOMContentLoaded', () => { ...@@ -26,16 +53,15 @@ window.addEventListener('DOMContentLoaded', () => {
26 } catch (e){ 53 } catch (e){
27 console.log(e); 54 console.log(e);
28 } finally { 55 } finally {
29 - console.log(result) 56 + console.log(result);
30 - result && $App && ($App.innerHTML = App('main')) 57 + result && $App && (()=>{
58 + $App.innerHTML = App('main');
59 + setState({insta_id : insta_id});
60 + })();
31 } 61 }
62 + } else {
63 + alert('아이디를 입력하세요');
32 } 64 }
33 } 65 }
34 }); 66 });
35 -
36 - window.addEventListener('popstate', ()=>{
37 - $App && ($App.innerHTML = App(pathname))
38 - });
39 -
40 - $App && ($App.innerHTML = App(pathname));
41 }) 67 })
...\ No newline at end of file ...\ No newline at end of file
......
1 +export {default} from './state';
...@@ -11,7 +11,7 @@ export interface StateType{ ...@@ -11,7 +11,7 @@ export interface StateType{
11 following? : Array<string> 11 following? : Array<string>
12 } 12 }
13 13
14 -export const state: StateType ={ 14 +const state: StateType ={
15 insta_id : '', 15 insta_id : '',
16 followers : [''], 16 followers : [''],
17 following : [''], 17 following : [''],
...@@ -20,4 +20,6 @@ export const state: StateType ={ ...@@ -20,4 +20,6 @@ export const state: StateType ={
20 export const setState = (newState: StateType): StateType =>({ 20 export const setState = (newState: StateType): StateType =>({
21 ...state, 21 ...state,
22 ...newState 22 ...newState
23 -});
...\ No newline at end of file ...\ No newline at end of file
23 +});
24 +
25 +export default state;
......
...@@ -4,21 +4,24 @@ ...@@ -4,21 +4,24 @@
4 * @description : 페이지 내용 4 * @description : 페이지 내용
5 **/ 5 **/
6 import Home from "./contents/Home"; 6 import Home from "./contents/Home";
7 +import '../../assets/style/Body.scss';
7 8
8 const Body = (pathname: string) : string => { 9 const Body = (pathname: string) : string => {
9 let contentsContainer = ''; 10 let contentsContainer = '';
10 11
11 switch (pathname){ 12 switch (pathname){
12 case 'compare': 13 case 'compare':
14 + /* 팔로워, 팔로잉 비교*/
13 break; 15 break;
14 case 'main': 16 case 'main':
17 + /* 아이디 조회 후 */
15 break; 18 break;
16 default: 19 default:
17 contentsContainer = Home(); 20 contentsContainer = Home();
18 } 21 }
19 22
20 return ` 23 return `
21 - <div class="Body"> 24 + <div class="body">
22 ${contentsContainer} 25 ${contentsContainer}
23 </div> 26 </div>
24 `; 27 `;
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
4 * @description id 입력하는 메인 화면 4 * @description id 입력하는 메인 화면
5 * 조회 / 업데이트 5 * 조회 / 업데이트
6 **/ 6 **/
7 -import {state} from "../../../state/state"; 7 +import state from "../../../state";
8 import Title from "../../../components/title"; 8 import Title from "../../../components/title";
9 9
10 const Home = (): string =>{ 10 const Home = (): string =>{
......
1 +export {default} from './Body'
...\ No newline at end of file ...\ No newline at end of file
...@@ -4,10 +4,12 @@ ...@@ -4,10 +4,12 @@
4 * @description : 페이지 푸터 4 * @description : 페이지 푸터
5 **/ 5 **/
6 6
7 +import '../../assets/style/Footer.scss'
8 +
7 const Footer = () : string => { 9 const Footer = () : string => {
8 10
9 return ` 11 return `
10 - <div class="Footer">Its my Footer</div> 12 + <div class="footer">Its my Footer</div>
11 `; 13 `;
12 } 14 }
13 15
......
1 +export {default} from './Footer'
...\ No newline at end of file ...\ No newline at end of file
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
5 **/ 5 **/
6 6
7 import Title from "../../components/title"; 7 import Title from "../../components/title";
8 +import '../../assets/style/Header.scss'
8 9
9 const Header = () : string => ` 10 const Header = () : string => `
10 <div class="header"> 11 <div class="header">
......
1 +export {default} from './Header'
...\ No newline at end of file ...\ No newline at end of file
...@@ -15,8 +15,8 @@ module.exports = { ...@@ -15,8 +15,8 @@ module.exports = {
15 exclude: /node_modules/, 15 exclude: /node_modules/,
16 }, 16 },
17 { 17 {
18 - test : /\.css$/, 18 + test : /\.scss$/,
19 - use: ['style-loader', 'css-loader'], 19 + use: ['style-loader', 'css-loader', 'sass-loader'],
20 }, 20 },
21 { 21 {
22 test: /\.(png|jpe?g|gif|jp2|webp)$/, 22 test: /\.(png|jpe?g|gif|jp2|webp)$/,
......
No preview for this file type
No preview for this file type
No preview for this file type
...@@ -17,12 +17,21 @@ firebase = pyrebase.initialize_app(config) ...@@ -17,12 +17,21 @@ firebase = pyrebase.initialize_app(config)
17 17
18 db = firebase.database() 18 db = firebase.database()
19 19
20 -def update_data(user_insta_id ,followers, followings):
21 - insta_id = user_insta_id.replace('_','').replace('.','')
22 20
23 - data = { 21 +def id_encrypt(user_insta_id):
24 - "followings" : followings, 22 + return user_insta_id.replace('_', '1z1').replace('.', '2z2')
25 - "followers" : followers 23 +
26 - } 24 +
25 +def update_data(user_insta_id, data):
26 + insta_id = id_encrypt(user_insta_id)
27 27
28 db.child("insta").child(insta_id).update(data) 28 db.child("insta").child(insta_id).update(data)
29 +
30 +
31 +def get_data_by_id(user_insta_id):
32 + insta_id = id_encrypt(user_insta_id)
33 +
34 + data = db.child("insta").child(insta_id).get()
35 +
36 + return data.val()
37 +
......
No preview for this file type
No preview for this file type
...@@ -3,7 +3,7 @@ from selenium import webdriver ...@@ -3,7 +3,7 @@ from selenium import webdriver
3 from selenium.webdriver.common.keys import Keys 3 from selenium.webdriver.common.keys import Keys
4 from config.admin import ID, PW, LOCAL_PROJECT_PATH 4 from config.admin import ID, PW, LOCAL_PROJECT_PATH
5 from config.URLs import INSTAGRAM_URL 5 from config.URLs import INSTAGRAM_URL
6 -# from config.firebase import update_data 6 +from config.firebase import update_data
7 7
8 def check_people(driver, type): 8 def check_people(driver, type):
9 result = [] 9 result = []
...@@ -43,7 +43,12 @@ def get_list(insta_id, driver): ...@@ -43,7 +43,12 @@ def get_list(insta_id, driver):
43 following_list = check_people(driver, "following") 43 following_list = check_people(driver, "following")
44 44
45 # update at firebase 45 # update at firebase
46 - # update_data(insta_id, followers_list, following_list) 46 + data = {
47 + "followers" : followers_list,
48 + "followings" : following_list,
49 + "insta_id" : insta_id
50 + }
51 + update_data(insta_id, data)
47 52
48 53
49 def crawler_instagram(insta_id): 54 def crawler_instagram(insta_id):
...@@ -64,10 +69,9 @@ def crawler_instagram(insta_id): ...@@ -64,10 +69,9 @@ def crawler_instagram(insta_id):
64 isPrivate = "" 69 isPrivate = ""
65 pass 70 pass
66 71
67 - # 비공개 계정인 경우 72 + # account is private
68 if isPrivate: 73 if isPrivate:
69 print('private!!') 74 print('private!!')
70 - # 공개 계정인 경우
71 else: 75 else:
72 get_list(insta_id, driver) 76 get_list(insta_id, driver)
73 77
......
...@@ -5,18 +5,16 @@ googleapis-common-protos==1.52.0 ...@@ -5,18 +5,16 @@ googleapis-common-protos==1.52.0
5 httplib2==0.19.0 5 httplib2==0.19.0
6 itsdangerous==1.1.0 6 itsdangerous==1.1.0
7 Jinja2==2.11.3 7 Jinja2==2.11.3
8 -jws==0.1.3
9 MarkupSafe==1.1.1 8 MarkupSafe==1.1.1
10 oauth2client==3.0.0 9 oauth2client==3.0.0
11 protobuf==3.15.2 10 protobuf==3.15.2
12 pyasn1==0.4.8 11 pyasn1==0.4.8
13 pyasn1-modules==0.2.8 12 pyasn1-modules==0.2.8
14 -pycryptodome==3.4.3
15 pyparsing==2.4.7 13 pyparsing==2.4.7
16 Pyrebase==3.0.27 14 Pyrebase==3.0.27
17 -python-jwt==2.0.1 15 +Pyrebase4==4.4.3
18 requests==2.11.1 16 requests==2.11.1
19 requests-toolbelt==0.7.0 17 requests-toolbelt==0.7.0
20 rsa==4.7.2 18 rsa==4.7.2
21 six==1.15.0 19 six==1.15.0
22 -Werkzeug==1.0.1 20 +Werkzeug==1.0.1
...\ No newline at end of file ...\ No newline at end of file
......
1 import os 1 import os
2 from flask import Flask, render_template, request, jsonify, send_from_directory 2 from flask import Flask, render_template, request, jsonify, send_from_directory
3 from crawler.crawler_instagram import crawler_instagram 3 from crawler.crawler_instagram import crawler_instagram
4 +from config.firebase import get_data_by_id
4 5
5 -# my_path = '/Users/choewonseog/Documents/check-your-instagram/app/public'
6 -# my_path = 'C:/Users/goesnow/Documents/study/check-your-instagram/app/public'
7 6
8 root_dir = os.path.dirname(os.getcwd()) 7 root_dir = os.path.dirname(os.getcwd())
9 my_path = os.path.join(root_dir, 'check-your-instagram', 'app', 'public') 8 my_path = os.path.join(root_dir, 'check-your-instagram', 'app', 'public')
...@@ -11,14 +10,29 @@ app = Flask(__name__, static_folder=os.path.abspath(my_path)) ...@@ -11,14 +10,29 @@ app = Flask(__name__, static_folder=os.path.abspath(my_path))
11 10
12 11
13 def update(insta_id): 12 def update(insta_id):
14 - crawler_instagram(insta_id) 13 + result = 'ok'
14 + try:
15 + crawler_instagram(insta_id)
16 + except Exception as e:
17 + print(e)
18 + result = 'fail'
15 19
16 data = { 20 data = {
17 - "insta_id" : insta_id 21 + "result" : result
18 } 22 }
19 return jsonify(data) 23 return jsonify(data)
20 24
21 25
26 +def search(insta_id):
27 + result = {}
28 + try:
29 + result = get_data_by_id(insta_id)
30 + except Exception as e:
31 + print(e)
32 +
33 + return jsonify(result)
34 +
35 +
22 @app.errorhandler(404) 36 @app.errorhandler(404)
23 def page_not_found(): 37 def page_not_found():
24 return render_template('404.html') 38 return render_template('404.html')
...@@ -27,11 +41,13 @@ def page_not_found(): ...@@ -27,11 +41,13 @@ def page_not_found():
27 @app.route("/", defaults={"path": ""}) 41 @app.route("/", defaults={"path": ""})
28 @app.route("/<path:path>") 42 @app.route("/<path:path>")
29 def home(path): 43 def home(path):
44 + insta_id = request.args.get('insta_id')
30 if path == 'update': 45 if path == 'update':
31 - insta_id = request.args.get('insta_id')
32 return update(insta_id) 46 return update(insta_id)
33 - 47 + elif path == 'search':
34 - return send_from_directory(my_path, filename='index.html') 48 + return search(insta_id)
49 + else:
50 + return send_from_directory(my_path, filename='index.html')
35 51
36 52
37 if __name__ == "__main__": 53 if __name__ == "__main__":
...@@ -39,4 +55,4 @@ if __name__ == "__main__": ...@@ -39,4 +55,4 @@ if __name__ == "__main__":
39 print(" server is start") 55 print(" server is start")
40 print("-" * 60) 56 print("-" * 60)
41 57
42 - app.run(debug=True) 58 + app.run(host="localhost", port=8080, debug=True)
......
1 -<!DOCTYPE html>
2 -<html>
3 -<head>
4 - <title>check insta</title>
5 -</head>
6 -<body>
7 - <h1>메인 화면</h1>
8 - <input id="insta_id" placeholder="id" type="text" name="insta_id"/>
9 - <button id="submit_id">제출</button>
10 - <script>
11 - const button = document.querySelector('#submit_id')
12 - const input = document.querySelector('#insta_id')
13 -
14 - button.addEventListener('click',async () => {
15 - let result = null;
16 -
17 - try {
18 - result = await (await fetch('http://localhost:5000/update?insta_id=' + input.value)).json()
19 - } catch (e) {
20 - console.log(e)
21 - } finally {
22 - if(result) {
23 - console.log(result.insta_id)
24 - }
25 - }
26 - })
27 - </script>
28 -</body>
29 -</html>
...\ No newline at end of file ...\ No newline at end of file