레시피

MM·2024년 4월 29일

AstroDeepDive

목록 보기
4/7
post-thumbnail

백엔드 서비스 추가

Firebase




SSR(output:'server')

API경로를 사용하여 양식 작성

HTML 양식을 사용하면 브라우저가 페이지를 새로 고치거나 새 페이지로 이동한다..!
-> 양식 제출을 JS로 가로채서 API 엔드포인트로 보내자!

//index.astro
---
import FeedbackForm from "../components/FeedbackForm"
---
<FeedbackForm client:load /> //SSR표시
  
  
  
//src/components/FeedbackForm.tsx
//양식 입력할 폼
import { useState } from "react";

export default function Form() {
  const [responseMessage, setResponseMessage] = useState("");

  async function submit(e: SubmitEvent) {
    e.preventDefault(); //기존 동작 없애고 
    const formData = new FormData(e.target as HTMLFormElement);
    
    //api 엔드포인트로 저장된 폼 데이터를 보내기
    const response = await fetch("/api/feedback", {
      method: "POST",
      body: formData,
    });
    const data = await response.json();
    
    if (data.message) setResponseMessage(data.message);
    
  }

  return (
    <form onSubmit={submit}>
      <label>
        Name
        <input type="text" id="name" name="name" required />
      </label>
      <label>
        Email
        <input type="email" id="email" name="email" required />
      </label>
      <label>
        Message
        <textarea id="message" name="message" required />
      </label>
      <button>Send</button>
      {responseMessage && <p>{responseMessage}</p>}
    </form>
  );
}


//src/pages/api/feedback.ts
//가로챈 양식을 보낼 api 엔드포인트
import type { APIRoute } from "astro";

export const POST: APIRoute = async ({ request }) => {
  const data = await request.formData();
  const name = data.get("name");
  const email = data.get("email");
  const message = data.get("message");
  
  // 데이터 유효성 검사
  if (name && email && message)
  return new Response(
    JSON.stringify({
      message: "Success!"
    }),
    { status: 200 }
  );
  
  else {
    return new Response(
      JSON.stringify({
        message: "Missing required fields",
      }),
      { status: 400 }
    );
  }
  
};


Astro 페이지에서 HTML 양식 작성

Astro 페이지는 SSR에서 직접 양식을 표시하고 처리할 수 있다!

---
import { isRegistered, registerUser } from "../../data/users"
import { isValidEmail } from "../../utils/isValidEmail";

const errors = { username: "", email: "", password: "" };

if (Astro.request.method === "POST") {
  try {
    const data = await Astro.request.formData();
    const name = data.get("username");
    const email = data.get("email");
    const password = data.get("password");
    
    //유효성 검사
    if (typeof name !== "string" || name.length < 1) 
      errors.username += "Please enter a username. ";
    
    if (typeof email !== "string" || !isValidEmail(email)) 
      errors.email += "Email is not valid. ";
    else if (await isRegistered(email)) 
      errors.email += "Email is already registered. ";
    
    if (typeof password !== "string" || password.length < 6) 
      errors.password += "Password must be at least 6 characters. ";
    
    const hasErrors = Object.values(errors).some(msg => msg)
    if (!hasErrors) {
      //작성된 양식을 db에 저장하고 login 페이지로 보내기
      await registerUser({name, email, password});
      return Astro.redirect("/login");
    }
  } catch (error) {
    if (error instanceof Error) {
      console.error(error.message);
    }
  }
}
---
<h1>Register</h1>
<form method="POST"> //GET인지 POST인지는 여기서 명시
  <label>
    Username:
    <input type="text" name="username" />
  </label>
  {errors.username && <p>{errors.username}</p>}
  <label>
    Email:
    <input type="email" name="email" required />
  </label>
  {errors.email && <p>{errors.email}</p>}
  <label>
    Password:
    <input type="password" name="password" required minlength="6" />
  </label>
  {errors.password && <p>{errors.password}</p>}
  <button>Register</button>
</form>


Captcha를 사용하여 검증하기

구글 recaptcha api를 사용하여 인간인지 컴퓨터인지를 검증하자!

//src/pages/index.astro
<html>
  <head>
	  //여긴 왜넣어주는거지
    <script is:inline src="https://www.google.com/recaptcha/api.js"></script> 
  </head>

  <body>
    <button class="g-recaptcha"
      data-sitekey="PUBLIC_SITE_KEY"
      data-callback="onSubmit"
      data-action="submit"> Click me to verify the captcha challenge! </button>

    <script is:inline>
      //Google reCAPTCHA API에서는 성공적으로 인증을 마치면 콜백 함수에 토큰이 전달됨!

      function onSubmit(token) {
      //여기서 token을 url로 보내 이게 통과인지 실패인지 확인해야 함!
        fetch("/recaptcha", {
          method: "POST",
          body: JSON.stringify({ recaptcha: token }) 
        })
        .then((response) => response.json())
        .then((gResponse) => {
          if (gResponse.success) {
            // Captcha 검증성공
          } else {
            // Captcha 검증실패
          }
        })
      }
    </script>
  </body>
</html>


//src/pages/recaptcha.js
export async function POST({ request }) {
  const data = await request.json();

  const recaptchaURL = 'https://www.google.com/recaptcha/api/siteverify';
  const requestHeaders = {
    'Content-Type': 'application/x-www-form-urlencoded'
  };
  
  const requestBody = new URLSearchParams({
    secret: "YOUR_SITE_SECRET_KEY",   // 구글 captcha 서비스 사용시 같이 주는 시크릿키
    response: data.recaptcha          // captcha 테스트 후 받아온 토큰
  });

  const response = await fetch(recaptchaURL, {
    method: "POST",
    headers: requestHeaders,
    body: requestBody.toString()
  });

  const responseData = await response.json();

  return new Response(JSON.stringify(responseData), { status: 200 });
}




동적으로 이미지 가져오기

로컬 이미지는 .astro 파일로 가져와야 한다!
-> import.meta.glob 함수를 사용하면 assets 폴더 내 확장자에 해당하는 모든 콘텐츠를 가져온다.

//src/components/MyCustomCardComponent.astro
---
import type { ImageMetadata } from 'astro';
import { Image } from 'astro:assets';

const extensions='/src/assets/*.{jpeg,jpg,png,gif}';

const { imagePath } = Astro.props;
const images = import.meta.glob<{ default: ImageMetadata }>(extensions);


/*
이런 형태나 마찬가지!
const images = {
  './assets/avatar-1.jpg': () => import('./assets/avatar-1.jpg'),
  './assets/avatar-2.png': () => import('./assets/avatar-2.png'),
  './assets/avatar-3.jpeg': () => import('./assets/avatar-3.jpeg')
}
*/

//유효성 검사
if (!images[imagePath]) throw new Error(`"${imagePath}" does not exist in glob: ${extensions}`);
---
<div class="card">
    <Image src={images[imagePath]()} /> //따라서 함수로 넣어야 함!
</div>


//src/pages/index.astro
---
import MyCustomCardComponent from '../components/MyCustomCardComponent.astro';
---
<MyCustomCardComponent
    imagePath="/src/assets/avatar-1.jpg" //이미지 경로
/>



외부 링크에 아이콘 추가

외부 사이트를 가리키는 Markdown 파일의 링크를 식별하고 수정할 수 있게 해주는 라이브러리

// astro.config.mjs
import rehypeExternalLinks from 'rehype-external-links';

export default defineConfig({
  // ...
  markdown: {
    rehypePlugins: [
      [
        rehypeExternalLinks,
        {
          content: { type: 'text', value: ' 🔗' } //외부링크 끝에 해당 아이콘이 추가된다
        }
      ],
    ]
  },
});
profile
중요한 건 꺾여도 그냥 하는 마음

0개의 댓글