0%

moment 라이브러리 대신 내장 Date 함수를 사용하면 충분하다.

  • JavaScript의 두 날짜를 <,<=,>및>=와 같은 비교 연산자로 직접 비교할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
var date1 = new Date('2020-10-23');
var date2 = new Date('2020-10-22');

console.log(date1 > date2);
console.log(date1 >= date2);
console.log(date1 < date2);
console.log(date1 <= date2);

>>>
true
true
false
false
  • 하지만, 등호 연산자를 사용하여 직접 Date 객체를 비교할 수 없다. JavaScript에서 다른 객체는 동일하지 않기 때문이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
let date1 = new Date();
let date2 = new Date(date1);

console.log(date1 == date2);
console.log(date1 === date2);
console.log(date1 != date2);
console.log(date1 !== date2);

>>>
false
false
true
true

사실 Gmail API를 사용해서 메일을 보내면 되는 태스크인데, api에 종속되지 않고 메일을 보낼 수 있는 방법이어서 시도해봤다. 서버 없이 프론트에서 form을 이용해 메일을 보낼 수 있는 방법이라 알아두면 유용할 것 같다.

Google apps script

본인 계정의 지메일로 구글 스프레드 시트를 deploy 함. (구글 드라이브에서 확인 가능)

https://docs.google.com/spreadsheets/… → Tools 탭 → Script Editor

* 테스트 후 Manage Deployment 에서 권한 Only Myself 로 설정! *


  • 보내는 사람: Google Apps 를 deploy한 사람

  • 받는 사람: mailData.receiver (내 form의 name="receiver" 이 받는 사람이기 때문)

    it defaults to the email provided by the form’s data attribute

  • var mailData = e.parameters === FormData body

    • e : formData

      e is the data received from the POST

    • e.parameters === mailData : formData.data

      shorter name for form data

    1
    2
    3
    4
    5
    6
    7
    8
    9
    /*

    formData // e
    data // e.parameters
    {"receiver": "", ...}

    */

    "data":"{\\"receiver\\":[\\"aaa@gmail.com\\"],\\"title\\":[\\"z\\"],\\"message\\":[\\"q\\"]}"

위는 내 폼 형식에 맞게 중요한 부분만 적어두었는데, 단계별로 진행해보고 싶다면 밑 레퍼런스를 참고하면 된다.

Reference

내 홈페이지에 이메일 보내기 기능 넣기! | form태그에서 이메일 전송 기능 구현하기 | 서버없이! html과 javascript로만! | 내 html에 추가하기 | html 이메일 보내기 기능 | 웹페이지에 이메일 기능 넣..

정적 HTML form태그에서 메일보내기 : Google Apps Mail

GitHub - dwyl/learn-to-send-email-via-google-script-html-no-server: An Example of using an HTML form (e.g: “Contact Us” on a website) to send Email without a Backend Server (using a Google Script) perfect for static websites that need to collect data.

순열

1
from itertools import permutations

permutations(iterable, 개수)

조합

1
from itertools import combinations

combinations(iterable, 개수)

모든 경우의 수

2차원 배열에서 각 배열 요소의 요소 값끼리 tuple을 구하고 싶을 때,

1
from itertools import product

product(*iterable)

  • 이 때 tuple의 원소 개수는 2차원 배열의 길이 (배열 요소의 개수) 이다.

bisect 라이브러리

bisect : 정렬된 배열을 유지하면서 특정 값을 배열에 추가할 때, 몇 번째 인덱스에 넣어야하는지 반환하는 함수

1
2
3
4
5
6
from bisect import bisect_left, bisect_right

nums = [0,1,2,3,4,5,6,7,8,9]
n = 5
print(bisect_left(nums, n)) >>> 5
print(bisect_right(nums, n)) >>> 6

bisect_left(literable, value) : 왼쪽 인덱스를 구하기

bisect_right(literable, value) : 오른쪽 인덱스를 구하기

  • 사용자가 usernames, passwords, 그리고 기타 개인적인 정보를 가지고 특정 서비스에 저장된 자신의 데이터를 다른 서비스에서 접근할 수 있도록 허락해주는 프로토콜
  • e.g. 구글의 서비스에서 관리되고 있는 사용자의 데이터를 다른 서비스가 접근할 수 있도록 구글 API를 제공

특징

  • 여러 장치를 통해 시시각각 바뀌는 사용자의 연락처 정보를 실시간으로 업데이트 받을 수 있음
  • 타 서비스에 저장된 사용자 데이터를 접근해도 되는지 사용자에게 명시적으로 허락을 받음

OAuth 2.0 flow for JavaScript: implicit grant flow

  • Implicit Grant
  • 자바스크립트 라이브러리로 개발되는 SPA에서는 implicit grant 방식이 많이 사용됨
  • user가 present 할 때만 API에 접근 가능하며, 기밀 정보는 따로 저장되지 않음
  1. 사용자는 API를 제공받고자 하는 서비스에 로그인 한 후, API를 사용하고자 하는 어플리케이션에서 요청하는 권한을 확인하고, 해당 권한을 허용한다.
  2. Google Authorization Server는 사용자를 사전에 등록된 redirect uri 로 리다이렉트 시키면서 access token 을 애플리케이션에 보낸다.
  3. 어플리케이션은 이 access token 을 이용해서 구글 API를 호출하여 원하는 정보를 얻는다.

Workflow

  1. Request : 클라이언트 어플리케이션은 Google Authorization Server에 access token 을 요청하고,
  2. Response : response로부터 token 을 추출한다.
  3. Validation : 이후 접근하고자 하는 Google API에 token 을 보낸다.

oauth2.0_javascript

Example: Google People API

  1. 구글 API를 호출하기 위해서는 가장 먼저, 구글에 API를 사용하고자 하는 어플리케이션을 클라이언트로 등록하고 client_id 를 발급받아야 한다.

    • 구글 API 콘솔 에서 프로젝트를 생성 → 사용자 인증 정보 만들기 에서 OAuth 클라이언트 ID를 선택 → 웹 어플리케이션 → 승인된 리디렉션 URI 설정 (e.g. [http://localhost:3000/](http://localhost:3000/) ) → 클라이언트 ID 생성
  2. Google Authorization Server에 접속하여 사용자 token 을 받아야 한다.

    • CLIENT_ID: 위에서 발급받은 클라이언트 ID
    • AUTHORIZE_URI: 구글에서 OAuth를 제공하는 기본 URI
    • queryStr: 위 AUTHORIZE_URI에 붙일 사용자 개인 정보 (쿼리), URL 생성용
      • client_id
      • redirect_uri: 실습 애플리케이션은 페이지가 하나 밖에 없는 SPA이기 때문에, 현재 URL을 redirect_uri로 설정
      • response_type: 어떤 OAuth 방식을 사용하는지를 결정하는 속성으로, token 으로 설정하면 implicit grant 방식이 적용
      • scope: 사용자의 어떤 데이터와 어떤 작업에 대한 권한을 요청하는지, 즉 어떤 구글 API를 요청하는지
    • loginUrl: AUTHORIZE_URIqueryStr을 붙여 만든 구글 로그인 authorization URL
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const CLIENT_ID = "YOUR CLIENT ID";
    const AUTHORIZE_URI = "https://accounts.google.com/o/oauth2/v2/auth";

    const queryStr = qs.stringify({
    client_id: CLIENT_ID,
    redirect_uri: window.location.href,
    response_type: "token",
    scope: "https://www.googleapis.com/auth/contacts.readonly"
    });

    const loginUrl = AUTHORIZE_URI + "?" + queryStr;
  3. Google Authorization Server가 redirect URIaccess token을 보내주면, 이를 이용해 token validation을 진행한다.

    • access token은 redirect uri의 hash 부분에 포함되므로, DOM의 Location API인 window.location.hash 를 이용해 가져온다.
    • 만약 access token이 없을 경우, grant server URL로 이동한다.
    1
    2
    3
    4
    5
    6
    const { access_token } = qs.parse(window.location.hash.substring(1));

    if (!access_token) {
    window.location.assign(loginUrl);
    return null;
    }
  4. access token을 이용해 구글 API를 호출한다.

    • Bearer 문자열을 access token 앞에 붙여 Authorization header로 넘긴다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    const PEOPLE_URI = "https://people.googleapis.com/v1/contactGroups";

    useEffect(() => {
    fetch(PEOPLE_URI, {
    headers: { Authorization: "Bearer " + access_token }
    })
    .then((res) => res.json())
    .then((data) => setContactGroups(data.contactGroups));
    }, [access_token]);

전체 코드

qs
npm 패키지: A querystring parsing and stringifying library with some added security.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import React, { useState, useEffect } from "react";

import qs from "qs";

const CLIENT_ID = "YOUR CLIENT ID";
const AUTHORIZE_URI = "https://accounts.google.com/o/oauth2/v2/auth";
const PEOPLE_URI = "https://people.googleapis.com/v1/contactGroups";

const queryStr = qs.stringify({
client_id: CLIENT_ID,
redirect_uri: window.location.href,
response_type: "token",
scope: "https://www.googleapis.com/auth/contacts.readonly"
});

const loginUrl = AUTHORIZE_URI + "?" + queryStr;

const App = () => {
const { access_token } = qs.parse(window.location.hash.substring(1));
const [contactGroups, setContactGroups] = useState([]);

useEffect(() => {
fetch(PEOPLE_URI, {
headers: { Authorization: "Bearer " + access_token }
})
.then((res) => res.json())
.then((data) => setContactGroups(data.contactGroups));
}, [access_token]);

if (!access_token) {
window.location.assign(loginUrl);
return null;
}

return (
<>
<h2>Contact Groups</h2>
<ul>
{contactGroups &&
contactGroups.map(({ resourceName, name, memberCount }) => (
<li key={resourceName}>
{name} ({memberCount})
</li>
))}
</ul>
</>
);
};

export default App;

Reference

OAuth 2.0으로 구글 API 호출하기

OAuth 2.0 Flow: Client-side web apps | YouTube Data API

  • OAuth 2.0 프로토콜 문서

Using OAuth 2.0 to Access Google APIs | Google Identity

  • OAuth 2.0 JavaScript 문서

OAuth 2.0 for Client-side Web Applications | Google Identity

  • Google People API 문서

Method: contactGroups.get | People API | Google Developers

보통, cra typescript를 위해서

1
npx create-react-app [project-name] --template typescript

를 쓰지만,

이번에는 직접 만들어보면서 react library의 작동 원리를 살펴보려고 한다.

Adding TypeScript | Create React App

세팅 과정

  1. npm을 세팅한다.

    1
    npm init -y // -y로 default 설정 적용
  2. npm pkg를 설치한다.

    1. typescript

      1
      npm i --s typescript @types/react @types/react-dom
    2. react

      1
      npm i --s react react-dom
    3. babel

      1
      npm i --s-d babel-loader @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript
    4. webpack

      1
      npm i --s-d webpack webpack-dev-server webpack-cli
  3. 타입스크립트 설정하기

    1. 타입스크립트 설정 파일을 만들기 위해,

      • 전역으로 설치된 경우 tsc --init
      • 해당 프로젝트에만 설치된 경우 직접 파일을 명시해야 하기 때문에 node_modules/.bin/tsc --init

      을 수행한다.

    2. 그럼 tsconfig.json 이 생성되는데, 다음과 같이 주석을 해지해주면 된다. (꼭 밑과 같지 않아도, 경우에 맞게/ 입맛에 맞게 설정해두면 두고두고 용이함!)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      {
      "compilerOptions": {
      "outDir": "./dist/",
      "sourceMap": true,
      "noImplicitAny": true,
      "module": "commonjs",
      "target": "es5",
      "jsx": "react"
      },
      "include": ["./src/**/*"]
      }
  4. 리액트 Entry File 설정

    보통 cra 시 다음과 같은 디렉토리를 갖게 된다. (직접 만들어야 하는 파일만 명시해둠)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    node_modules
    public
    index.html
    src
    App.tsx
    index.tsx
    package.json
    tsconfig.json
    ...

    따라서 먼저 Component를 만들어보자.

    1
    import * as React from "react";// 함수형 컴포넌트const App: React.FunctionComponent<{ name: string }> = ({ children, name }) => (  <div title={name}>{children}</div>);export default App;

    그리고 html에서 script로 읽을 index.js를 생성하자.

    1
    import * as React from "react";import * as ReactDOM from "react-dom";import App from "./App";// 클래스형 컴포넌트class Root extends React.Component {  render() {    return (      <div>        <App name="test"> test </App>      </div>    );  }}ReactDOM.render(<Root />, document.getElementById("root"));

    원래는 따로 클래스 정의 없이 ReactDOM.render() 에 클래스 컴포넌트 렌더 안에 들어있는 코드를 적어 바로 컴포넌트를 전달한다. 축약하면 다음과 같다.

    1
    ReactDOM.render(  <App />,  document.getElementById('root'));

    이제 최종적으로 js 파일을 전달할 public/index.html 을 정의하자!

    1
    <!DOCTYPE html><html lang="en">  <head>    <meta charset="UTF-8" />    <title>채용 프로세스 이메일 템플릿</title>  </head>  <body>    <div id="root"></div>    <!-- 번들 파일 추가 -->    <script src="./bundle.js"></script>  </body></html>

    html은 id="root" 를 통해 index 파일의 document.getElementById("root") 를 읽어나가기 때문에, 따로 script를 적용하지 않는다. 하지만, 이 src/index.js 를 읽기 위해 webpack 번들러를 설정하였으므로, 해당 bundle.js 파일을 script로 적용해야 한다!

    <div id="root"></div> 밑에 전달해야 한단다.
    내 생각에는 html을 읽는 순서 때문에 그런듯.

  5. Babel 설정하기

    1. trans-compile을 위해 .babelrcbabel.config.js 로 설정한다.

      1
      {  "presets": [    "@babel/preset-env",    "@babel/preset-react",    "@babel/preset-typescript"  ]}
  6. webpack 설정하기

    1. 이 모든 과정을 연결하기 위해 번들러 webpack을 설정한다. 바벨과 같이 webpack.config.js 를 루트 폴더에 만들면 됨!

      1
      module.exports = {  entry: ["./src/index.tsx"],  output: { filename: "bundle.js", publicPath: "/", path: __dirname + "dist" },  module: {    rules: [      {        test: /\.(js|jsx|ts|tsx)$/,        exclude: /node_modules/,        use: ["babel-loader"]      }    ]  },  resolve: { extensions: ["*", ".js", ".jsx", ".ts", ".tsx"] }};

      resolve: 확장자나 경로를 알아서 처리할 수 있도록 설정하는 옵션이다.
      module: 이 옵션에 설치한 ts-loader와 babel-loader를 설정하면 된다. loader들은 오른쪽에서 왼쪽 방향으로 적용되기 때문에 ts-loader를 babel-loader보다 오른쪽에 위치시켜야 한다.
      output: 번들화 된 파일을 export할 경로와 파일명을 설정한다.

  7. 마지막으로, package.json 에서 script 를 설정해주면 된다.

    1. npm start 또는 npm run start 로 script를 실행할 수 있도록 다음과 같이 설정하면 됨!

      npm run 시 script 내부의 명령어을 적용할 수 있다!

      1
      "scripts": {    "start": "webpack-dev-server --config ./webpack.config.js --mode development",    "test": "echo \"Error: no test specified\" && exit 1"  },

FAQ

  • 웹팩 설정에서 사용된 inline 모드는 무엇일까?

    네트워크 방화벽 구성과 동일하게 모든 트래픽이 해당 보안장비를 거쳐야만 목적지로 전송될 수 있도록 네트워크를 구성하는 방식

    [네트워크] 인라인 모드와 미러링(스니핑) 모드의 차이점

  • hot 모드는?

    Hot Module Replacement(HMR)는 응용프로그램 실행 중에 추가 또는 제거된 모듈들을 페이지 리로드 없이 교체하는 기능입니다.

Reference

리액트 - 타입스크립트 시작하기 (without CRA)

위 블로그 기반으로,

CRA없이 React + TypeScript 셋팅하기! - chanyeong

위 블로그는 심화형임

Rebase

  • 말 그대로, Rebase : base를 변경하는 것
  • 현재 브랜치의 작업 내용을 변경하지 않고 rebase 하고자 하는 base의 상태를 가져오는 것

Pull

  • pull하고자 하는 브랜치의 모든 내용을 가져오는 것

정규식 문자열 앞에 r 문자를 삽입하면 이 정규식은 Raw String 규칙에 의하여 백슬래시 2개 대신 1개만 써도 2개를 쓴 것과 동일한 의미를 갖게 된다.

1
re.sub(r'\') ## '\\'

※ 만약 백슬래시를 사용하지 않는 정규식이라면 r의 유무에 상관없이 동일한 정규식이 될 것이다.

re.sub()

re.sub(pattern, repl, string, count=0, flag=0)

: patternrepl 로 대체한 string 을 반환한다.

  • repl\ 가 존재할 경우 backslash escape 가 적용된다.

Example

  • 알파벳 소문자, 숫자, 빼기(-), 밑줄(_), 마침표(.)를 제외한 모든 문자를 제거

    1
    2
    re.sub(r"[^a-z0-9-_.]", "", answer)
    # '[^a-z\d\-\_\.]'
  • 마침표(.)가 2번 이상 연속된 부분을 하나의 마침표(.)로 치환

    1
    2
    re.sub('\.\.+', '.', answer)
    # '((.)\\2{1,})'
  • 마침표(.)가 처음이나 끝에 위치한다면 제거

    1
    2
    re.sub("^[.]|[.]$", "", answer)
    # '^\.|\.$'

Input

첫째 줄에 단어의 개수 N이 주어진다. (1 ≤ N ≤ 20,000)

둘째 줄부터 N개의 줄에 걸쳐 알파벳 소문자로 이루어진 단어가 한 줄에 하나씩 주어진다. 주어지는 문자열의 길이는 50을 넘지 않는다.

1
2
3
4
5
import sys
input = sys.stdin.readline

n = int(input())
s = set(input().rstrip() for _ in range(n))

Sort

  • 같은 단어가 여러번 입력된 경우에는 한 번씩만 출력해야 하므로 set 를 이용하고,
  • 조건에 따라 sort 를 구현하는데, set 에는 sort 가 없으므로 sorted 를 이용해야 한다.
1
s = sorted(s, key=(lambda x: (len(x), x)))

Output

조건에 따라 정렬하여 단어들을 출력한다. 단, 같은 단어가 여러 번 입력된 경우에는 한 번씩만 출력한다.

1
2
for word in s:
print(word)

Input

첫째 줄에 점의 개수 N (1 ≤ N ≤ 100,000)이 주어진다.

둘째 줄부터 N개의 줄에는 i번점의 위치 xi와 yi가 주어진다. (-100,000 ≤ xi, yi ≤ 100,000) 좌표는 항상 정수이고, 위치가 같은 두 점은 없다.

1
2
3
4
5
6
7
8
import sys
input = sys.stdin.readline

n = int(input())
s = []

for _ in range(n):
s.append(list(map(int, input().split())))

Sort

sort 함수에 key 를 이용하면 쉽게 해결되는데, 이를 위해서 sort 함수를 살펴보자.

sort()

Basic Syntax

1
list.sort(key=..., reverse=...)

Parameters

  • key : sort 수행 시 비교 를 위해 적용되는 함수

    • e.g. 1D

      1
      list.sort(key=len)

      len 함수에 따라 리스트의 요소의 길이가 정렬 기준이 된다.

    • e.g. 2D

      tuple의 경우, 첫번째 파라미터가 디폴트로 정렬 기준이 된다.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      # random list
      random = [(2, 2), (3, 4), (4, 1), (1, 3)]

      # sort list with key
      random.sort(key=(lambda x: x[1]))

      ### lambda to function declaration
      # key=takeSecond
      # def takeSecond(elem):
      # return elem[1]

      위의 예시에서는, 튜플 x 의 2번째 파라미터를 기준으로 정렬을 수행한다.

  • reverse : 기본 오름차순, True 시 내림차순

Return Value

None 👉 원본을 수정한다.

sorted()

sorted list 👉 원본을 수정하지 않고, 새로운 리스트를 반환한다.

위에 따라 key 를 지정해서 sort 함수를 커스터마이징하면,

1
s.sort(key=(lambda x: (x[0], x[1])))

이는 즉, 튜플 x 의 1번째 인자를 첫 기준으로 두고, 2번째 인자를 두번째 기준으로 둔다 는 의미이다.

Output

첫째 줄부터 N개의 줄에 점을 정렬한 결과를 출력한다.

1
2
for i in s:
print(i[0], i[1])