Project Fake Search 3-1サーバのページ機能の実装と設定(サイト名とテーマ色、クエリーの自動完了)


開始します。


牧業ページが完成し,まず検索ページ設定以外の設定機能の実現を開始する.ユーザ情報をデータベースに格納する部分は,一度に実現すると便利であるため,Sequelizeの移行や会員加入に関するAPIのように,まずサーバに必要な部分を実現する.

サーバ実装


Sequeizeの設定

//migrations/AutoCompletes
'use strict';
module.exports = {
  up: async (queryInterface, Sequelize) => {
    await queryInterface.createTable('AutoCompletes', {
		...
      userId: {
        type: Sequelize.INTEGER,
        onDelete: 'CASCADE',
        references:{model: 'Users', key: 'id'}
      },
		...
    });
  },
  down: async (queryInterface, Sequelize) => {
    await queryInterface.dropTable('AutoCompletes');
  }
};
//models/AutoCompletes
'use strict';
const {
  Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
  class AutoComplete extends Model {
		...
    static associate(models) {
      AutoComplete.belongsTo(models.User,{
        foreignKey: "userId",
        onDelete: "CASCADE",
      })
		...
};
以前のプロジェクトでは、Sequelizeで多くのエラーが発生したため(https://github.com/codestates/Shall-We-Health/projects/3)、本プロジェクトではすべてのエラーの解決方法を見つけ、必要なクエリー文をまとめ、Raw Queryがなく、Sequelizeメソッドのみですべての機能を実現できると考えた.ただし、Sequeizeはバックエンドチームメンバーが担当しているため、CASCADEに関する設定を行うには迷いましたが、modelsもmigrationsもonDelete: 'CASCADE'の設定が必要であることを知り変更しました.
訪問者のログアウトと会員の脱退時のユーザー.idを外部キーとするテーブルのデータも自動的に削除する必要があるため,外部キーを設定するとともにカスケード設定を行った.

ソーシャル・ログインの実施(NAVER)

//네이버 로그인 요청
const jwt = require("jsonwebtoken");
const { User } = require("../../models/index");
const axios = require("axios");

module.exports = async (req, res) => {
    const { token } = req.body;
    if (!token) {
      return res.status(400).json({
        data: null,
        error: {
          path: "users/naver",
          message: "Insufficient body data",
        },
      });
    }
    const userData = await axios.get("https://openapi.naver.com/v1/nid/me", {
      headers: {
        Authorization: `Bearer ${token}`,
      },
    });
    const identification = userData.data.response.id;
  
    try {
      const [user, created] = await User.findOrCreate({
        where: { identification },
        defaults: {
            oauth: 'naver',
            siteName: 'FAKESEARCH',
            themeColor:'#2260FF',
        },
      });
  
      if (!created) {
        /* 회원가입이 되어있을 때 */
        const accessToken = jwt.sign(
            user.dataValues,
          process.env.ACCESS_SECRET,
          { expiresIn: "7d" }
        );
  
        res
          .cookie("accessToken", accessToken)
          .status(200)
          .end();
      } else {
        /* 회원가입 되어 있지 않을 때 */
        const accessToken = jwt.sign(
          user.dataValues,
          process.env.ACCESS_SECRET,
          { expiresIn: "7d" }
        );
        res
          .cookie("accessToken", accessToken)
          .status(201)
          .end();
      }
    } catch (err) {
		...
    };
設定機能を実現するためには、ユーザのデータを格納する必要があるため、NAVER登録から実現する.クライアントがNaverに要求して受信したtokenをサーバに転送すると、サーバはNaverにユーザ情報を受信し、findOrCreateメソッドを使用してユーザ情報を問合せたり、再挿入したりしてaccessTokenをCookieに入れて応答します.
以前のプロジェクトではKACAとNAVEROAuthの登録が実現していたので、スピーディーに進んでいました.セキュリティ上の理由により、naverログインはサーバ環境でのみユーザー情報を要求できますが、KACAとGoogleはクライアントで直接ユーザー情報を伝達するので、中間のaxoisリクエストを除いて同じ論理で実現します.

設定機能の実装


サイト名とトピックの色の設定


//checkSiteName.js
export default function checkSiteName(siteName) {
    const name = siteName
    const checkForm = new RegExp(/^[가-힣a-zA-Z]{2,10}$/);
    const checkDomain = ['naver','daum','kakao','google','nate','네이버','다음','카카오','구글','네이트'] 
    
    if(!checkForm.test(name) || name === null || checkDomain.some((el) => name.toLowerCase().includes(el))) {
        return false
    } else return true
}
Webサイト名を変更する機能は簡単な論理ですが、Webサイト名をチェックする関数も作成されています.ハングル、英語の2〜10文字しか含まれておらず、悪用される可能性があるため、有名ポータルサイト名を含まない単語に設定した.
//사이트 이름 및 테마 색상 설정
import React, { useState, useEffect, useRef } from 'react';
import Color from './Color';
		...

export default function Site() {
  
  const { themeColor, siteName } = useSelector((state) => state.loginReducer)
  const btnColor = useRef()
  const boxColor = useRef()
  const [modalColor, setModalColor] = useState(false)
		...

  const handleClickOutside = ({ target }) => {
    if (!boxColor.current.contains(target) && !btnColor.current.contains(target)) setModalColor(false);
  };

  const handleCheck = (e) => {
    setNicknameForm(e.target.value)
    if(e.target.value==='') {
      setIsChecked(true)
    } else {
      setIsChecked(checkSiteName(e.target.value))
    }
  }
		...
        
useEffect(() => {
  window.addEventListener("click", handleClickOutside);
  return () => {
    window.removeEventListener("click", handleClickOutside);
  };
}, []);

  return (
    <div className='site-container'>
		...
      <div className='box-theme'>
        <div className='title-theme'>테마 색상</div>
        <div className='info-color' ref={btnColor}>
          <div id='sample-color' style={{backgroundColor: themeColor}} onClick={()=>{setModalColor(!modalColor)}}/>
          <div id='text-color' onClick={()=>{setModalColor(!modalColor)}}>{themeColor}</div>
        </div>
        <Color boxColor={boxColor} modalColor={modalColor} themeColor={themeColor}/>
      </div>
    </div>
  )
}
トピックカラー適用セクションではreact-colorライブラリを使用し、useref設定を使用してカラーまたはカラーコードをクリックしたときに設定ウィンドウを表示し、外部をクリックしたときに閉じます.
//Color.js
		...
export default function Color({boxColor, modalColor }) {

  const dispatch = useDispatch();
  const { themeColor } = useSelector((state) => state.loginReducer)

  const hadleThemeColor = (e) => {
    axios.patch(`${process.env.REACT_APP_SERVER_API}/users/theme-color`, {
      themeColor : e.hex
    },{withCredentials: true})
    .then (()=>{
      dispatch(login({themeColor : e.hex}))
    })
  }

  return <div  ref={boxColor} className={modalColor ? 'color-container' : 'hidden'}>
    <ChromePicker color={themeColor} onChange={hadleThemeColor}/>
  </div>;
}
上記のコードは、色変更時にサーバに色コードを送信受信し、Reducerに値を変更した部分で、最初はマニュアルのthemeColor : eのように書かれていましたが、エラーが発生しました.console.log(e)であることを確認すると、e自体は色コード値ではなく、複数の値を持つオブジェクトであるため、伝達に必要なハースコードに変更された.

自動補完クエリーの設定


//Main.js
import React, { useState, useEffect, useRef } from 'react';
import filterAutoComplete from '../utils/filterAutoComplete';
		...
export default function Main() {
		...
  const [searchWord, setSearchWord] = useState('');
  const [autoComplete, setAutoComplete] = useState([]);
  const [focus, setFocus] = useState(false);
  const [modal, setModal] = useState(false);
 		... 

  const handleSeachWord = async (e) => {
    setSearchWord(e.target.value);
    if (
      filterAutoComplete(e.target.value) !== '' &&
      e.target.value.replace(/(\s*)/g, '') !== '' &&
      isLogin
    ) {
      const word = filterAutoComplete(e.target.value);
      const res = await axios.get(
        `${process.env.REACT_APP_SERVER_API}/auto/filtered`,
        { params: { word }, withCredentials: true }
      );
      setAutoComplete(res.data);
    }
    if (filterAutoComplete(e.target.value).replace(/(\s*)/g, '') === '') {
      setAutoComplete([]);
    }
  };

  return (
        <div className='main-container'>
		...
          <div className='searchForm-container'>
		...
                  <input
                    type='text'
                    className='search'
                    onChange={handleSeachWord}
                    onFocus={() => {
                      setFocus(true);
                    }}
                    onBlur={() => {
                      setFocus(false);
                    }}
                    onKeyPress={(e) => {
                      if (e.key === 'Enter') {
                        window.location.replace(`/search/query=${searchWord}`);
                      }
                    }}
                  ></input>
		...
                {!(searchWord === '' && autoComplete.length === 0) && focus &&
                    autoComplete.map((el, id) => {
                      return (
                        <AutoList
                          key={id}
                          el={el}
                          searchWord={searchWord}
                          themeColor={themeColor}
                        ></AutoList>
                      );
                    })}
               	...
          </div>
        </div>
  );
}
//filterAutoComplete.js
export default function filterAutoComplete(autoWord) {
    return autoWord.replace(/[^가-힣a-zA-Z0-9~!@#$%^&*()_+|<>?:{};\-=`.,/ ]+/g,'')
  }
自動完了検索語を追加・削除する機能は簡単ですが、実際に検索ウィンドウで見た部分には思いがけないエラーがあるので条件を追加しました.自動完了検索語가, 가나, 가나다この3つの語があれば가ㄴのように子音だけ入力しても自動完了検索語は가, 가나, 가나다に表示されるべきで、inputウィンドウでフォーカスを外すと自動完了単語リストが見えなくなるようにfilterAutoComplete関数と複数のstateを作成して条件掛けを行います.

の最後の部分


今回のプロジェクトでは、reactをより深く理解し、エラーを処理し、機能に必要な論理を考え出す過程に意味があると思います.新しいスキルを学ぶのもいいですが、以前使っていたスタックを深く理解する必要があります.さまざまなスタックを体験し、使いこなすことが望ましいが、新しい開発者として基礎を固めることが重要らしい.