["나의 ESG궁합" 개발 일기] (251114) 구글 로그인 유저 가치관 진단 생략

■ 소개

https://esgsaju.web.app/

.

목표: 419 (사업으로 1달러 벌기 시작해서 9달러까지 벌어보기)

.

어제 회식 여파로 아침에는 못하구 뒤늦게 올려봅니다.

내일 모각때 다른 걱정없이 바이브코딩 집중하고싶네요 ㅎ

■ 진행 및 결과

1) DB설계

구글 로그인을 한번이라도 했던 유저는 ESG가치관 진단한 내역이 DB에 저장되도록 했습니다.

firebase기반으로 db를 재구축해야할 것 같아.

구글로그인 시 User UID별로 ESG 가치관 진단 결과를 저장하는 구조 짜줘

.

18기 뇌과학때 준님으로부터 DB 설계의 기초를 배웠습니다.

그래서 구글 로그인시 DB가 쌓이는 기준이 되는 User UID별로 가치관을 저장하는 구조를 만들어달라고 했습니다.

.

그 결과 나온 db.ts는 다음과 같습니다.

(근데 ts가 뭔지 잘 모르겠습니다)

import { collection, doc, getDoc, getDocs, serverTimestamp, setDoc, addDoc, query, orderBy, limit } from 'firebase/firestore';
import type { User as FirebaseUser } from 'firebase/auth';
import { db } from '../firebase';

export interface CompanyMatchRecord {
  companyId: string;
  companyName: string;
  totalScore: number;
  eScore: number;
  sScore: number;
  gScore: number;
  matchType: string;
  timestamp: number;
}

export interface StoredUserProfile {
  nickname: string;
  type: string;
  typeDescription: string;
  eScore: number;
  sScore: number;
  gScore: number;
  sensitiveTopic: string;
}

export async function ensureUserDocument(user: FirebaseUser): Promise<boolean> {
  const ref = doc(db, 'users', user.uid);
  const snap = await getDoc(ref);
  const existed = snap.exists();
  if (!existed) {
    await setDoc(ref, {
      uid: user.uid,
      displayName: user.displayName ?? null,
      email: user.email ?? null,
      photoURL: user.photoURL ?? null,
      provider: 'google',
      createdAt: serverTimestamp()
    });
  } else {
    await setDoc(
      ref,
      {
        displayName: user.displayName ?? null,
        email: user.email ?? null,
        photoURL: user.photoURL ?? null,
        provider: 'google',
        updatedAt: serverTimestamp()
      },
      { merge: true }
    );
  }
  return existed;
}

export async function saveCompanyMatch(uid: string, match: CompanyMatchRecord) {
  const ref = doc(db, 'users', uid, 'matches', match.companyId);
  await setDoc(
    ref,
    {
      ...match,
      timestamp: match.timestamp || Date.now()
    },
    { merge: true }
  );
}

export async function fetchUserMatches(uid: string): Promise<CompanyMatchRecord[]> {
  const ref = collection(db, 'users', uid, 'matches');
  const snap = await getDocs(ref);
  const items: CompanyMatchRecord[] = [];
  snap.forEach((docSnap) => {
    const data = docSnap.data() as Partial<CompanyMatchRecord>;
    if (!data.companyId) {
      // ensure companyId from doc id
      data.companyId = docSnap.id as any;
    }
    items.push({
      companyId: data.companyId!,
      companyName: data.companyName ?? '',
      totalScore: data.totalScore ?? 0,
      eScore: data.eScore ?? 0,
      sScore: data.sScore ?? 0,
      gScore: data.gScore ?? 0,
      matchType: data.matchType ?? '',
      timestamp: typeof data.timestamp === 'number' ? data.timestamp : Date.now()
    });
  });
  // sort by timestamp desc
  items.sort((a, b) => b.timestamp - a.timestamp);
  return items;
}

export async function userDocumentExists(uid: string): Promise<boolean> {
  const ref = doc(db, 'users', uid);
  const snap = await getDoc(ref);
  return snap.exists();
}

export async function saveUserProfile(uid: string, profile: StoredUserProfile): Promise<void> {
  const ref = doc(db, 'users', uid);
  await setDoc(
    ref,
    {
      profile,
      profileUpdatedAt: serverTimestamp()
    },
    { merge: true }
  );
}

export async function fetchUserProfile(uid: string): Promise<StoredUserProfile | null> {
  const ref = doc(db, 'users', uid);
  const snap = await getDoc(ref);
  if (!snap.exists()) return null;
  const data = snap.data() as any;
  if (!data?.profile) return null;
  const p = data.profile as Partial<StoredUserProfile>;
  return {
    nickname: p.nickname ?? '사용자',
    type: p.type ?? '환경 수호자',
    typeDescription: p.typeDescription ?? '',
    eScore: Number(p.eScore ?? 50),
    sScore: Number(p.sScore ?? 50),
    gScore: Number(p.gScore ?? 50),
    sensitiveTopic: p.sensitiveTopic ?? ''
  };
}

export interface ESGDiagnosisRecord {
  nickname: string;
  type: string;
  typeDescription: string;
  eScore: number;
  sScore: number;
  gScore: number;
  sensitiveTopic: string;
  createdAt: number;
}

export async function saveESGDiagnosis(uid: string, profile: StoredUserProfile): Promise<void> {
  const ref = collection(db, 'users', uid, 'diagnoses');
  await addDoc(ref, {
    nickname: profile.nickname,
    type: profile.type,
    typeDescription: profile.typeDescription,
    eScore: profile.eScore,
    sScore: profile.sScore,
    gScore: profile.gScore,
    sensitiveTopic: profile.sensitiveTopic,
    createdAt: serverTimestamp()
  });
}

export async function fetchLatestESGDiagnosis(uid: string): Promise<ESGDiagnosisRecord | null> {
  const q = query(collection(db, 'users', uid, 'diagnoses'), orderBy('createdAt', 'desc'), limit(1));
  const snap = await getDocs(q);
  if (snap.empty) return null;
  const docSnap = snap.docs[0];
  const data = docSnap.data() as any;
  return {
    nickname: data.nickname ?? '사용자',
    type: data.type ?? '환경 수호자',
    typeDescription: data.typeDescription ?? '',
    eScore: Number(data.eScore ?? 50),
    sScore: Number(data.sScore ?? 50),
    gScore: Number(data.gScore ?? 50),
    sensitiveTopic: data.sensitiveTopic ?? '',
    createdAt: typeof data.createdAt?.toMillis === 'function' ? data.createdAt.toMillis() : Number(data.createdAt ?? Date.now())
  };
}

export async function fetchAllESGDiagnoses(uid: string): Promise<ESGDiagnosisRecord[]> {
  const q = query(collection(db, 'users', uid, 'diagnoses'), orderBy('createdAt', 'desc'));
  const snap = await getDocs(q);
  const items: ESGDiagnosisRecord[] = [];
  snap.forEach((docSnap) => {
    const data = docSnap.data() as any;
    items.push({
      nickname: data.nickname ?? '사용자',
      type: data.type ?? '환경 수호자',
      typeDescription: data.typeDescription ?? '',
      eScore: Number(data.eScore ?? 50),
      sScore: Number(data.sScore ?? 50),
      gScore: Number(data.gScore ?? 50),
      sensitiveTopic: data.sensitiveTopic ?? '',
      createdAt: typeof data.createdAt?.toMillis === 'function' ? data.createdAt.toMillis() : Number(data.createdAt ?? Date.now())
    });
  });
  return items;
}

.

그 뒤에는 이제 Firebase에 이런식으로 DB가 저장되고 있는게 확인됩니다.

중앙에 별표가 있는 Google 검색 페이지의 스크린샷

.

2) 구글 로그인 사용자 가치관 진단 반복 조사 방지

를 하기 위해 아래와 같이 프롬프트를 커서에 넣었습니다.

기존에 구글 로그인한 유저는 매번 설문조사를 할 필요가 없어. 그러니까 구글 로그인을 선택하면 기존 유저는 바로 HoneScreen으로 가게 해줘(안녕하세요, 사용자님! 이 뜨는 화면, 캡쳐올림)으로 가도록 해줘@HomeScreen.tsx 

그리고 "사용자님" 문구에 구글아이디를 반영해줘

.

한번에 해결이 안되기에 왜 그런지 알아봤더니,

화면에는 보이지 않는 "궁합 저장"버튼이 있더라구요.

그걸 없애고 설문조사 마치면 자동으로 저장되게 해달라고 했습니다.

궁합 저장 버튼 안뜸. 그리고 필요없어. 그냥 설문조사 마치면 자동으로 저장되게 해줘

그렇게 하니

이제 구글 로그인을 한번이라도 한 유저는 자동으로 홈화면으로 이동하게 되었습니다.

.

■ 느낀점 및 향후 계획

이제 적절한 DB를 받아 컨텐츠를 마련해야겠습니다.

컨텐츠는 AI에게 메인으로 맡기지 않고,

제가 구상하려고 합니다.

.

낼 모각에서 뵐게요-!

1
1개의 답글

뉴스레터 무료 구독

👉 이 게시글도 읽어보세요