기록
  • Day 7 - 깃허브 작업
    2024년 09월 12일 18시 14분 38초에 업로드 된 글입니다.
    작성자: 삶은고구마
    • 코드 정리
    • 깃허브로 누이터 호스팅
    • 에러발생 ,보안정책 수정하기

     


    1.코드 정리

     

    1)home 정리 

    Home.js는 여러기능을 포함하고있어서 기능단위로 정리하여 컴포넌트로 나누려한다.

    하단의 form 부분이 트윗 생성 역할을 하므로 이 부분을 컴포넌트화 하자.

    컴포넌트 폴더 하위에 NweetFactory.js 생성

    const NweetFactory = () =>{
    };
    
    export default NweetFactory;

     

    Home.js

    return (
            <>
            {/* 기존 폼을 컴포넌트화 함 */}
            <NweetFactory/>
            <div>
                {nweets.map((nweet)=>(
                    <Nweet 
                        key = {nweet.id} 
                        nweetObj={nweet}
                        isOwner={nweet.creatorId === userObj.uid}
                    />
                ))}
            </div>
    
            </>
        );

    기존 폼에서 사용하던 함수들이 새로 이전한 NweetFactory 컴포넌트에 정의되지 않아 발생하는 에러.

    여기서 사용하는 함수들을 모두 옮겨야 한다.

     

    NweetFactory.js

    const NweetFactory = () =>{
        const onSubmit = async (event) =>{
            //새로고침 방지 
            event.preventDefault();
            let attachmentUrl="";
            if(attachment!==""){
                const attachmentRef = storageService.ref().child(`${userObj.uid}/${uuidv4()}`);
                const response = await attachmentRef.putString(attachment,"data_url");
                attachmentUrl = await response.ref.getDownloadURL(); 
            }
            await dbService.collection("nweets").add({
                text: nweet,
                createdAt : Date.now(),
                creatorId : userObj.uid,
                attachmentUrl,
            });
            setNweet("");
            setAttachment(""); //초기화
    
            // console.log(await response.ref.getDownloadURL());
        };
    
        const onChange = (event) =>{
            event.preventDefault();
            const{
                target:{value},
            }=event;
            setNweet(value);
        };
    
        const onFileChange = (event) =>
        {
            // console.log(event.target.files);
            const{
                target:{files},
            }=event;
            const theFile = files[0];
            const reader = new FileReader();
            reader.onloadend = (finishedEvent)=>{
                // console.log(finishedEvent);
                const{
                    currentTarget:{result},
                }=finishedEvent;
                setAttachment(result);
            };
            reader.readAsDataURL(theFile);
        }
    
        //파일 선택 취소 버튼
        const onClaerAttachment = () => setAttachment("");
    
    
    
    
    
    
        return (
            <>
            <form onSubmit={onSubmit}>
                <input
                    value={nweet}
                    onChange={onChange}
                    type="text"
                    placeholder="What's on your mind?"
                    maxLength={120}
                />
                <input type="file" accept ="image/*" onChange={onFileChange}/>
                <input type="submit" value="Nweet"/>
                <br/>
                <div>
                {attachment && (
                    <div>
                        <img src={attachment} width="100px" height="100px"/>
                        <br/>
                        <button onClick={onClaerAttachment}>Clear</button>
                </div>
            )}
                
                </div>
            </form>
            <div>
                {nweets.map((nweet)=>(
                    <Nweet 
                        key = {nweet.id} 
                        nweetObj={nweet}
                        isOwner={nweet.creatorId === userObj.uid}
                    />
                ))}
            </div>
    
            </>
        );
    };
    
    export default NweetFactory;

     

    이사를 마친 후 Home.js

    import Nweet from "components/Nweet";
    import { dbService,storageService } from "fbase";
    import {useEffect, useState} from "react";
    import {v4 as uuidv4} from "uuid";
    
    const Home = ({userObj}) => {
        //console.log(userObj);
        const [nweet,setNweet] = useState("");
        const [nweets,setNweets] = useState([]);
        const [attachment, setAttachment] = useState("");
    
        //실시간 db 위한 onSnapshot함수적용
        useEffect(()=>{
           dbService.collection("nweets").onSnapshot((snapshot)=>{
            const newArray = snapshot.docs.map((document)=>({
                id : document.id,...document.data(),
            }));
            setNweets(newArray);
           });
        },[]);
        
        return (
            <>
            {/* 기존 폼을 컴포넌트화 함 */}
            <NweetFactory/>
            <div>
                {nweets.map((nweet)=>(
                    <Nweet 
                        key = {nweet.id} 
                        nweetObj={nweet}
                        isOwner={nweet.creatorId === userObj.uid}
                    />
                ))}
            </div>
    
            </>
        );
    
    };
        
    export default Home;

     

    그래도 오류가 발생할 것이다.

    이유는 NweetFactory 컴포넌트로 옮긴 함수나 jsx에서 사용하는 상태,프롭스,임포트 문을 정의해주지 않았기 때문.

    에러가 발생하는 부분은 텍스트가 흐릿해지는데 이걸 이동하면 된다..

     

     

    NweetFactory.js

    import { dbService,storageService } from "fbase";
    import {useState} from "react";
    import {v4 as uuidv4} from "uuid";
    
    
    const NweetFactory = () =>{
    
        const [nweet,setNweet] = useState("");
        const [attachment, setAttachment] = useState("");

     

    수정 완료된 코드들

    Home.js

    더보기
    import Nweet from "components/Nweet";
    import { dbService} from "fbase";
    import {useEffect, useState} from "react";
    import NweetFactory from "components/NweetFactory";
    
    const Home = ({userObj}) => {
        const [nweets,setNweets] = useState([]);
    
        //실시간 db 위한 onSnapshot함수적용
        useEffect(()=>{
           dbService.collection("nweets").onSnapshot((snapshot)=>{
            const newArray = snapshot.docs.map((document)=>({
                id : document.id,...document.data(),
            }));
            setNweets(newArray);
           });
        },[]);
        
        return (
            <>
            {/* 기존 폼을 컴포넌트화 함 */}
            <NweetFactory userObj={userObj}/>
            <div>
                {nweets.map((nweet)=>(
                    <Nweet 
                        key = {nweet.id} 
                        nweetObj={nweet}
                        isOwner={nweet.creatorId === userObj.uid}
                    />
                ))}
            </div>
    
            </>
        );
    
    };
        
    export default Home;

     

    NweetFactory.js

    더보기
    import {useState} from "react";
    import { dbService,storageService } from "fbase";
    import {v4 as uuidv4} from "uuid";
    
    const NweetFactory = ({userObj}) =>{
        const [nweet,setNweet] = useState("");
        const [attachment, setAttachment] = useState("");
    
        const onSubmit = async (event) =>{
            //새로고침 방지 
            event.preventDefault();
            let attachmentUrl="";
            if(attachment!==""){
                const attachmentRef = storageService.ref().child(`${userObj.uid}/${uuidv4()}`);
                const response = await attachmentRef.putString(attachment,"data_url");
                attachmentUrl = await response.ref.getDownloadURL(); 
            }
            await dbService.collection("nweets").add({
                text: nweet,
                createdAt : Date.now(),
                creatorId : userObj.uid,
                attachmentUrl,
            });
            setNweet("");
            setAttachment(""); //초기화
    
            // console.log(await response.ref.getDownloadURL());
        };
    
        const onChange = (event) =>{
            event.preventDefault();
            const{
                target:{value},
            }=event;
            setNweet(value);
        };
    
        const onFileChange = (event) =>
        {
            // console.log(event.target.files);
            const{
                target:{files},
            }=event;
            const theFile = files[0];
            const reader = new FileReader();
            reader.onloadend = (finishedEvent)=>{
                // console.log(finishedEvent);
                const{
                    currentTarget:{result},
                }=finishedEvent;
                setAttachment(result);
            };
            reader.readAsDataURL(theFile);
        }
    
        //파일 선택 취소 버튼
        const onClaerAttachment = () => setAttachment("");
    
    
    
    
    
    
        return (
            <>
            <form onSubmit={onSubmit}>
                <input
                    value={nweet}
                    onChange={onChange}
                    type="text"
                    placeholder="What's on your mind?"
                    maxLength={120}
                />
                <input type="file" accept ="image/*" onChange={onFileChange}/>
                <input type="submit" value="Nweet"/>
                <br/>
                <div>
                {attachment && (
                    <div>
                        <img src={attachment} width="100px" height="100px"/>
                        <br/>
                        <button onClick={onClaerAttachment}>Clear</button>
                </div>
            )}
                
                </div>
            </form>
    
            </>
        );
    };
    
    export default NweetFactory;

     

    2)Auth 정리 

    auth도 home과 마찬가지로 form부분을 컴포넌트화 한다.

    컴포넌트 폴더에 AuthForm 컴포넌트 파일을 만든 후 

    폼과 관련 함수들을 옮기면된다.

     

    AuthForm.js

    import { authService} from "fbase";
    import { useState } from "react";
    
    const AuthForm = () =>{
        const [email,setEmail] = useState("");
        const [password,setPassword] = useState("");
        const [newAccount,setNewAccount] = useState(true);
        const [error,setError] = useState("");
    
        const onChange = (event)=>
            {
                // console.log(event.target.name);
                const{
                    target : {name,value},
                } = event;
                if(name==="email"){
                    setEmail(value);
                }
                else if(name==="password"){
                    setPassword(value);
                }
         };
    
         const onSubmit = async (event)=>
            {
                event.preventDefault();
                try{
                    let data;
                    if(newAccount)
                        {
                            //create newAccount 
                            data = await authService.createUserWithEmailAndPassword(email,password);
                        }
                        else{
                            //log in
                            data = await authService.signInWithEmailAndPassword(email,password);
                        }
                        console.log(data);
                    }
                    catch(error)
                    {
                        setError(error.message);
                        // console.log("에러로그:",error);
                    }
               
            };
    
            const toggleAccount = () => setNewAccount((prev)=>!prev);
    
            // form,span태그 감싸기 위해 <>..</> 태그 사용
            return(
                <>
                
                    <form onSubmit={onSubmit}>
                        <input 
                            name="email" 
                            type = "email" 
                            placeholder="Email" 
                            required 
                            value={email} 
                            onChange={onChange}/>
                        <input 
                            name="password" 
                            type = "password" 
                            placeholder="Password" 
                            required
                            value={password}
                            onChange={onChange}/>
                        <input type = "submit" value = {newAccount ? "Create Account" : "Log in"}/>
                        <br/>
                        {error}
                    </form>
                    
                        <span onClick={toggleAccount}>
                            {newAccount ? "Sign In" : "Create Account"}
                        </span>         
                    </>  
            );
      
    };
    
    export default AuthForm;

     

    Auth.js

    import { authService,firebaseInstance} from "fbase";
    import AuthForm from "components/AuthForm";
    
    const Auth = () =>{
     
        const onSocialClick = async (event) =>{
            //console.log(event.target.name);
            try{
            const{
                target : {name},
            } = event;
            let provider;
            if(name=="google")
            {
                provider = new firebaseInstance.auth.GoogleAuthProvider();
            }
            else if(name=="github")
            {
                provider = new firebaseInstance.auth.GithubAuthProvider();
            }
            const data = await authService.signInWithPopup(provider);
            console.log(data);
            }
            //팝업창을 강제로 닫거나 기타 등등 문제
            catch(error)
            {
                if (error.code === 'auth/popup-closed-by-user') {
                    // 사용자에게 팝업을 닫았음을 알리고 다시 시도하도록 유도
                    alert('팝업이 닫혔습니다. 다시 시도해주세요.');
                  } else {
                    // 다른 오류 처리
                    console.error(error);
                  }
            }
            };
            
    
        return(
            <div>
                <AuthForm/>
                <div>
                    <br/>
                    <button onClick={onSocialClick} name="google">Continue with Google</button>
                    <button onClick={onSocialClick} name="github">Continue with Github</button>
                </div>
            </div>
        );
    };
    export default Auth;

     

    여기까지 작업을 수행 한 후, 로그아웃을 하는데 로그아웃 버그가 발생했다.

    새로고침을 해야 로그인 화면이 나타난다.

    원인은 refreshUser 함수 부분이다.

     

    아래 코드 useEffect 로직을 살피면 user가 없는 경우 userObj를 업데이트 하는 로직이 없음.

    즉, user가 없어지면 userObj도 비워야 하는데 그런 로직이 없어서 새로고침을 해야만 로그아웃된것처럼 보인다.

    else 구문에 userObj를 비워줄 함수 추가하면된다.

     

    App.js

     useEffect(()=>{
        authService.onAuthStateChanged((user)=>{
          if(user) //여기서 로그인 상태 isLoggedIn 변경
          {
            // setIsLoggedIn(user);
            // setUserObj(user);
            setUserObj({
              uid:user.uid,
              displayName:user.displayName,
              updateProfile:(args)=>user.updateProfile(args),
            });
          }else
          {
            setIsLoggedIn(false);
          }
          setInit(true);//init상태 변경
        });
      },[]);

     

    수정 후 App.js

    import { useEffect,useState } from "react";
    import AppRouter from "components/Router";
    import { authService } from "../fbase";
    
    function App() {
      const[init,setInit] = useState(false);
      // const [isLoggedIn,setIsLoggedIn] = useState(false);
      const [userObj,setUserObj] = useState(null);
      useEffect(()=>{
        authService.onAuthStateChanged((user)=>{
          if(user) //여기서 로그인 상태 isLoggedIn 변경
          {
            // setIsLoggedIn(user);
            // setUserObj(user);
            setUserObj({
              uid:user.uid,
              displayName:user.displayName,
              updateProfile:(args)=>user.updateProfile(args),
            });
          }else
          {
            // setIsLoggedIn(false);
            setUserObj(false);
          }
          setInit(true);//init상태 변경
        });
      },[]);
      //  setInterval(()=>console.log(authService.currentUser),2000);
    
      //userObj 새로고침
      const refreshUser=()=>{
        //setUserObj(authService.currentUser);
        const user = authService.currentUser;
        setUserObj({
          uid:user.uid,
          displayName:user.displayName,
          updateProfile:(args)=>user.updateProfile(args),
    
        });
      };
    
    
      return (
        <>
        {init? (
          <AppRouter 
            refreshUser={refreshUser}
            isLoggedIn={Boolean(userObj)} 
            userObj={userObj}/> 
          ): ("initializing...")
        }
        {/* jsx에 js 사용시 중괄호로 감싸기 */}
        {/* <footer>&copy; {new Date().getFullYear()}Nwitter</footer> */}
        </>
      
      );
    }
    export default App;

     

     


    2.깃허브로 누이터 호스팅

    호스팅이란?

    타인에게 내가 만든 누이터를 쓸 수 있도록 인터넷에 공개 하는 것이다.

     

    1)package.json 수정

    참조할 주소 확인

     

    homepage 양식 

    https:// + 깃헙아이디+ github.io/nwitter

     

    package.json 수정

    {
      "homepage": "https://jindamgom.github.io/nwitter",
      "name": "nwitter",
      "version": "0.1.0",

     

    다음으로는 호스팅을 위한 명령어 추가 입력.

    누이터를 깃허브에 호스팅하려면 웹에서 쓰이는 파일로 빌드 해야함.

     "scripts": {
        "start": "react-scripts start",
        "build": "react-scripts build",
        "predeploy": "npm run build",
        "deploy" :"gh-pages -d build"
      },

     

    터미널

    npm install gh-pages

     

    2)호스팅하기

    터미널 

    npm run deploy

     

    오류발생

    'E:\clonecoding\projects\nwitter\buld'

    오타가 발생해서 난 오류.build인데 buld라고 적어놨다 ㅠㅠ 

    오타 수정 후 다시 실행

    오류 없이 published라고 출력되면 정상적으로 호스팅 된것이다.

    그리고 입력한 홈페이지 주소대로 주소창에 입력 후 들어가면...출력이됨 

     


    3.에러 발생, 보안정책 수정

    1)소셜 로그인 하기

     

    파이어베이스 보안 문제로 내 깃헙 도메인이 인증되지 않았기 때문에 로그인이 되지 않는 것이다.

     

    2)파이어베이스 접속 허용 설정

    즉, 등록된 주소만 파이어베이스를 쓸 수있게 해주겠다는 의미다. 아무한테 주는 것이 아니다.

    책과는 설정 경로가 달라서 240912 금일 파베 버전으로 경로를 알려주겠다.

    Authentication -> 상단 탭 중 설정 -> 승인된 도메인 ->도메인 추가

    도메인 추가 시 http:// 혹은 https:// 제외하고 입력

     

    다시 소셜 로그인 시도하면 정상적으로 실행됨.

     

    3)파이어스토아 보안 정책 수정

    파이어스토어 - 규칙 선택 

    현재 계정이 사용하는 파이어스토어 서비스에 있는 모든 db에 대해 접근권한기간은 아래와 같고, 누구든지 

    read/write를 허용한다는 걸 의미한다 한다.

     match /{document=**} {
          allow read, write: if request.time < timestamp.date(2024, 10, 11);
        }
      }

     

    이걸 변경하고자 한다. 로그인하지 않은 사람에게 권한을 주지 않는다.

    match /{document=**} {
          allow read, write: if 
          // request.time < timestamp.date(2024, 10, 11);
          request.auth !=null
        }

     

     

    4)스토리지 보안 정책 확인

    스토리지 - 규칙 선택

    이것 역시 로그인한 사람만 허용.

    match /{document=**} {
          allow read, write: if 
          // request.time < timestamp.date(2024, 10, 11);
          request.auth !=null
        }

     

     

    5)비밀키 보안 설정

    앞의 작업에서 파이어베이스 비밀키를 숨겼었다. 

    이번엔 그 키를 나의 누이터에서만 동작하도록 설정할 것이다.

    비밀키가 내 누이터에서만 동작하려면 승인된 도메인 목록에 있는 파이어베이스 주소/깃허브 주소가 필요하다.

    그것들을 복사 한 후, 

     

    프로젝트 개요 - 톱니바퀴 버튼 - 프로젝트 설정

     

     

    상단 탭 메뉴 중 서비스 계정 선택

     

     

    api 및 서비스 - 사용자 인증 정보

     

    api키 - browser key 선택

     

     

    애플리케이션 제한사항 설정에 기본값으로 없음에 체크되어있는데 이걸 웹사이트로 변경한다.

    책에서는 http리퍼러(웹사이트)라고 되어있는데 괄호안에 웹사이트로 항목이 변경된 듯 하다.

    웹사이트를 선택하면 하단에 웹사이트 제한사항이 나타난다.

    여기에 아까 복사한 파이어베이스 주소와 깃허브 주소,로컬호스트를 추가하면 된다..

     

     

    '공부 > 클론코딩' 카테고리의 다른 글

    Day 7 - CSS 작업 (完)  (1) 2024.09.12
    Day 7 - 누이터 서브 기능 만들기  (1) 2024.09.12
    Day 5 - 로그인 기능 구현하기 1  (0) 2024.09.09
    Day 4 - 클론 코딩 시작 2  (2) 2024.09.09
    Day 3 - 클론 코딩 시작  (0) 2024.09.07
    댓글