반응형

 

 

 프로그램 30분 이상 사용하지 않으면 종료가 되는 문제가 발생했다. 

 

처음 이 코드를 의심했지만 해결되지 않았다. 

 

-- web.xml 

  <session-config>
    <session-timeout>180</session-timeout>
  </session-config>

 

 

바꿔보았다. 

 

 

-- app-config.xml 

<property name="minIdle"     value="5" />
<property name="validationQuery" value="SELECT 1 FROM SYSIBM.SYSDUMMY1" />

 

이제 끊어지지 않는다. 

 

 

참고문서 

https://stackoverflow.com/questions/10684244/dbcp-validationquery-for-different-databases

 

DBCP - validationQuery for different Databases

I use DBCP pool and I want use testOnBorrow and testOnReturn to test if connection is still valid. Unfortunately I have to set property validationQuery to make it work. Question: What value should...

stackoverflow.com

 

반응형
반응형

그룹핑이 되어 있어도 전체 합계를 구하여 비율을 계산하는 쿼리를 작성해야 했는데, 처음에는 단순히 

전체 쿼리를 한 번 더 덮어씌워서 그룹핑 하기 전 합계를 구해와야 하나 막막했다. 

 

그러다가 SQL 함수 중 OVER() 라는 함수를 알게 되었다. 

 

왜 기존에 한 번도 이 함수 쓰는 것을 못 보았나 싶었는데 생각해보니 총 행 수를 카운팅할 때 OVER( order by ... ) 을 쓰고 있었다. 

 

📌  OVER() 라는 함수란? 

 

윈도우 함수와 함께 사용되어, 데이터를 그룹화(GROUP BY)하여 행을 줄이지 않고도, 각 행을 유지하면서 순위, 합계, 평균 등 행간 계산을 수행하게 해주는 구문이다. 

 

OVER()는 유령 상자를 만드는 것이라고 생각하면 된다. 기존 데이터 줄은 건드리지 않고, 옆에 계산용 상자를 하나 더 만들어서 필요한 값을 담아주는 역할을 한다. 

 

순위 매길 때 

SELECT 
    ITEM_NAME,
    QTY_OUT,
    RANK() OVER(ORDER BY QTY_OUT DESC) AS '순위'
FROM OUT_TABLE

 

그룹별 계산할 때 

SELECT 
    CATEGORY,
    ITEM_NAME,
    PRICE,
    SUM(PRICE) OVER(PARTITION BY CATEGORY) AS '카테고리별_총합'
FROM OUT_TABLE

 

 

누적 합계 구할 때 

SELECT 
    OUT_DATE,
    QTY_OUT,
    SUM(QTY_OUT) OVER(ORDER BY OUT_DATE) AS '누적출고량'
FROM OUT_TABLE

 

전체 대비 비중을 구할 때(점유율, 비율)

SELECT 
    ITEM_NAME,
    QTY_OUT,
    SUM(QTY_OUT) OVER() AS '전체합계'
FROM OUT_TABLE
반응형
반응형

백엔드를 구성하고 프론트도 앱을 빌드하였다. 

 

프론트는 엑스포를 사용하기 때문에 간단하게 끝낼 수 있었다. 

 

# eas 에 로그인이 되어 있는지 확인 
eas whoami

# 현재 프로젝트 정보를 확인 
eas project:info;

# aab 생성
eas build

 

추가로 gitignore 에 DS_Store 를 추가해주었다. (에러가 뜨더라구요) 

   ❯ git commit -m "Fix: filename casing and ignore .DS_Store"

 

 

build 시간이 좀 걸린다. 기다리고 나면 링크가 하나 뜨는데 그 링크를 연결해 들어가면 자동으로 aab 이 다운받아진다.

 

그리고 구글 플레이 개발자 콘솔로 들어가서 app bundle 에 올려준다.

 

그리고 앱 검토를 받게 되면 게시할 수 있게 되고,  게시를 한다면 테스터 12명 이상 모아 14일 후에 테스트가 모두 완료되면 최종적으로 등록할 수 있다.  (사실 이렇게까지 할 줄 몰랐는디.. 12명의 지인이 없는 사람은... ㅠㅠ ) 

 

 

지인들에게 보여주기 전, 직접 다운로드 받고 테스트해보았다. 

충격이다... 로컬에선 빠르게, 잘 돌아가던 API 가 굉장히 느렸다. 서버 문제인지, 코드 성능 문제인지는 모르겠다. 

아무래도 더 업그레이드가 필요한 것 같다. 

 

 

 

잘 실행되는 것을 볼 수 있다. 이제 차근차근 앱 고도화를 할 것이다. 

또한,, AI, 딥러닝, 머신러닝 분야도 슬슬 관심이 생기기 시작했다.. 

반응형
반응형

안녕하세요, 앱 개발하고 있는 찹쌀떡입니다.  

 

어제 하루와 오늘 아침에 열심히 Railway 서버에서 열심히 굴린 결과, 배포에 성공하고 잘 작동합니다. 

 

포스트맨에서 새 도메인주소로 데이터를 보낸 결과입니다. 히히

 

 


 

사실 배포에 대한 고민이 좀 많았던 터라 지피티랑  제미니 부여잡고 계속 물어봤다.  

 

1. 배포를 한 지가 3년전. 아마 2023년 막학기 쯤에 Django 웹을 배포했을 때이다. 그 때는 책에 있는 내용 따라서 하였어도 힘들었는데 이제는 혼자서 해결해야 하니 막막했다. 

2. 비용이다. 비용을 저얼대 들이지 말아야지 라는 생각으로 가장 저렴하게 배포할 수 있는 방법을 많이 탐색했다. 

 

쨋든 위 2가지를 종합해본 결과, 2번에 따라 Railway 를 결정하였고 요즘은 몇년 전과 다르게 배포가 쉬운 상태라 1번은 자동으로 해결되었다. 

 

그러나, RailWay 를 가입하고 프로젝트를 만들었는데 무료가 아니더라.. 

 

 

 

30일에 5$ 를 준다. 30일 뒤엔 요금이 부과되더라 흑흑 

 

쨋든 그건 30일 후에 차차 다른 서버를 알아보던가, 앱 고도화 진행하며 유지보수를 하던가 결정하기로 미뤄두었다.  

 

이제, 정리를 하고자 한다. 에러도 많았으며 고생도 많이 했다. 

깊이 들어가진 않고 실무자 입장에서만 작성하였다.

 


 

RailWay 의 동작 방식? 

- GitHub 에 서버 코드를 올린다. 

- Railway 의 프로젝트와 Git 레포지토리와 연결한다. 

- Railway가  빌드와 디플로이를 한다.

- 오류가 난다면? 코드 혹은 환경변수를 수정하고 Git에 푸시하면 Railway 가 자동으로 해준다.

더보기

Deployments(배포/배치) : 개발된 소프트웨어나 서비스, 혹은 자원을 실제 사용 가능한 상태로 환경에 배치하는 것을 뜻

==> 우리가 할 건 깃허브에 폴더 올리기 + 빌드/디플로이 실패 로그를 보며 소스를 수정하고 다시 깃헙에 푸시하기   

 

지금 난 client 와 server 를 각각 관리하고 있다. 

깃허브에는 이 server 폴더만 올리면 된다. 

 

이 레포지토리를 만들었다. 

혹시 몰라 private 로 해주었다.  

 

그러면 Git 의 기본 명령어를 활용하여 폴더를 올려준다.

개인적으로 난 이렇게 했지만 상황에 따라 다르게 해준다. 

 

# 1. 연결되어 있는 원격 저장소 확인
git remote -v

# 2. 기존 연결 지우기 (옵션: 새로 연결하고 싶을 때만)
# -v는 붙이지 않습니다.
git remote remove origin

# 3. 새로운 원격 저장소 연결   
git remote add origin [저장소-주소]

# 4. 상태 확인 및 초기화 (필요한 경우에만)
git status
# 만약 아예 새로 시작하고 싶다면 git init (주의: 기존 기록 다 날아감)

# 5. 변경사항 스테이징 및 커밋
git add .
git commit -m "buddi server"

# 6. 브랜치 이름 확인  
git branch -M main

# 7. 푸시  
# 처음 푸시할 때는 -u를 써주면 다음부터는 git push만 해도 됩니다.
git push -u origin main

# 기타 (로컬 파일로 덮어 씌우기)
git push -f origin main

 

이렇게 해주면 깃헙에 잘 올라간 것을 볼 수 있다. 

 

그리고 이제 railway 에서 자동으로 빌드해준다. 아래와 같이 실패한 것이 좀 많다. 

 

 

동일 내용을 깃헙에서도 확인이 가능하다. 총 14번의 배포 시도가 있었다. 

 

가장 헤맸던 것이 dist/src/main.js 를 찾을 수 없다. 는 메시지였다. 여기서 몇시간은 잡아먹었다.  

 

 

변경사항을 천천히 정리해보겠다. 

 

일단, 빌드 상황에서 아래와 같은 에러가 뜨길래 compilerOptions 를 바꿔줘야 하나 싶어서 초반에 여러 수정을 했었다. 

(이는 로컬에도 개발했을 때 종종 뜨던 에러라 알기가 쉬웠다.) 

error TS5095: Option 'bundler' can only be used when 'module' is set to 'preserve' or to 'es2015' or later.

그래서 commonjs 에서 esnext 로 바꿔주었다. 

 

근데 또 떴다... 흠

다시 esnest 에서 NodeNest 로 변경해주었는데, 빌드까진 문제 없이 잘 돌아갔다.  

 

배포를 하는 도중 계속 dist/main.js 가 뜨길래, 경로를 확인하니 

 

npm run build
ls -R dist

 

main.js 가 dist 경로가 아닌 src 밑에 있었기에 수정해주었는데 또 똑같은 에러가 떴다. 

그래서 상대경로가 아닌 절대결로 바꿔보기도 하고 dockfile을 추가하여 빌드해보기도 했지만, 전혀 해결할 수 없었다. 

# DockerFile
# 1. 노드 버전 설정
FROM node:22.22.1-slim

# 2. 작업 디렉토리
WORKDIR /app

# 3. 의존성 설치 (캐싱 활용)
COPY package*.json ./
RUN npm install

# 4. 소스 복사 및 빌드
COPY . .
RUN npm run build

# 5. [핵심] 파일 위치를 확인하고 실행
# dist/src/main.js 에 있으면 그걸 실행하고, 없으면 dist/main.js를 실행합니다.
CMD if [ -f "dist/src/main.js" ]; then node dist/src/main.js; else node dist/main.js; fi

 

그래서, 개발환경과 다른 모듈을 재설정했다는 것이 문제가 아니었을까? 하는 생각에 

 

다시 초기상태로 복구해주었고, 

expo 가 서버에선 필요가 없어 제거했고, exclude 에 dist 를 넣어 주었다. 

 

그랬더니 문제없이 잘 돌아갔고, supabseUrl 을 못 찾겠다는 메시지가 나와서 railway 환경변수에 .env 파일에 있던 변수들을 모두 직접 넣어주었다. 

아래와 같이 Online 으로 잘 뜨면 된 것이다. 

Settings 에 가서 도메인을 복사해준다. 

 

클라이언트에선 axios url 을 다음과 같이 바꾸어주었다. (android에서만 쓸거라서 해당 부분만 변경) 

export const baseUrls = {
  android: "https://buddi-server-production.up.railway.app/",
  ios: "http://192.168.0.16:3030",
};

 

 

그러면 localhost 를 돌리지 않고도 잘 동작한다. 

 

반응형
반응형

이미지 업로드를 supabase storage 로 활용해서 하려고 하는데 ... 3일 고생했다. 정리해보도록 하자. 

 

흐름 

  1. 글 작성에서 폰에 있는 이미지를 불러와 이미지를 업로드할 수가 있다. 
  2. 이미지가 글 작성란에 미리보기로 뜰 수 있다. 
  3. 저장을 하면 이미지의 uri 와 정보가 담겨 db 에 저장된다. 

기존 구조 

  1. imagePicker 를 사용해서 폰에 있는 이미지를 업로드할 수 있다. 
  2. 저장을 하면 구현한 함수와 함께 db에 uri 객체로 저장이 된다. 

바꿀 구조 

  1. imagePicker 를 사용해서 폰에 있는 이미지를 supabase storage  에 업로드할 수 있다. 
  2. 저장을 하면 구현한 함수와 함께  db 에 url 과 이미지 정보를 저장한다. 
  3. 이미지를 불러올 때 db 에서 테이블을 조회하고 url 을 통해 storage 에 실시간으로 불러올 수 있다. 

 

바뀌는 게 별로 없기 때문에 쉬울 거라고 생각했지만, 생각보다 쉽지 않았다. 문서들을 보다가 활용하는 방식이 너무 달랐기 때문이다. 

 

그래서 현재 코드를 보면서 어느 부분을 수정해야 할 지부터 정리하기로 했다. 

 

기존 코드  

<ImagePreviewList imageUris={postForm.watch().imageUris} />  //write

 

여기서 이미지를 미리 볼 수 있다. 

//ImagePreviewList
import { baseUrls } from "@/api/axios";
import { ImageUri } from "@/types";
import { router } from "expo-router";
import React from "react";
import {
  Image,
 ...
} from "react-native";

interface ImagePreviewListProps {
  imageUris: ImageUri[];
}

function ImagePreviewList({ imageUris = [] }: ImagePreviewListProps) {
  return (
    <ScrollView
      horizontal
      showsHorizontalScrollIndicator={false}
      contentContainerStyle={styles.container}
    >
    
    // 내가 올린 이미지 미리 보기 
      {imageUris.map(({ uri }, index) => {
        const imageUri = `${
          Platform.OS === "ios" ? baseUrls.ios : baseUrls.android
        }/${uri}`;

        return (
          <Pressable
            key={uri + index}
            style={styles.imageContainer}
            onPress={() =>
              router.push({ pathname: "/image", params: { uri: imageUri } })
            }
          >
            <Image style={styles.image} source={{ uri: imageUri }} />
          </Pressable>
        );
      })}
    </ScrollView>
  );
}

... 
export default ImagePreviewList;

 

imageUris 을 map 으로 돌려 ${uri} 객체를 읽어 이미지로 보여준다.  

☑️ 바꿀 point --> baseUrl 을 storage public bucket Url 로 변경 

//useUploadImages
import { uploadImages } from "@/api/image";
import { useMutation } from "@tanstack/react-query";

function useUploadImages() {
  return useMutation({
    mutationFn: uploadImages,
  });
}

export default useUploadImages;

 

이미지들의 정보를 db 에 저장한다. 바꿀 포인트 없음. 

//api
import axiosInstance from "./axios";

async function uploadImages(body: FormData): Promise<string[]> {
  const { data } = await axiosInstance.post("/images", body, {
    headers: {
      "Content-Type": "multipart/form-data",
    },
  });

  return data;
}

export { uploadImages };

 

서버로 전송하는 함수. 바꿀 포인트 없음 

//utils/image
import * as ImagePicker from "expo-image-picker";

function getFormDataImages(
  key: string,
  images: ImagePicker.ImagePickerAsset[]
) {
  const formData = new FormData();

  images.forEach(({ uri, mimeType = "image/jpeg" }) => {
    const file = {
      uri,
      type: mimeType,
      name: uri.split("/").pop(),
    };

    formData.append(key, file as unknown as File);
  });

  return formData;
}

export { getFormDataImages };
  • 이 함수는 ImagePicker로 선택한 이미지 → 서버 업로드용 FormData 변환 역할
  • supabase Storage로 업로드하거나, 자체 서버 API로 업로드할 때 바로 사용 가능
  • key만 맞춰주면 여러 이미지도 한 번에 업로드 가능

바꿀 포인트 없음 ! 

 baseUrl 을 storage public bucket Url 로 변경해주고 , 제대로 설정하기 위해 .env 파일 및 이미지 호출 및 저장 url 을 변경해주면 된다. 

 

처음에는 as unknown as File 로 변환했는데 

 

 

  • 브라우저 환경 → File 객체로 업로드 가능
  • React Native → blob 그대로 업로드가 더 안전 + 호환성 좋음
  • 이름/타입 필요하면 blob에 name/type 속성 붙여서 처리

 

 

참고문서 1번,  

 

일단, 서버에 Storage 를 사용하기 위해서 .env 파일에 다음과 같이 적용해준다. 

// server/.env
# supabase config
SUPABASE_URL=... 
SUPABASE_SERVICE_ROLE_KEY=...

 

supabase 파일또한 만들어준다. 

import { createClient } from '@supabase/supabase-js';
import 'dotenv/config'; // 또는 require('dotenv').config()
console.log(process.env.SUPABASE_URL);
console.log(process.env.SUPABASE_SERVICE_ROLE_KEY);

export const supabase = createClient(
  process.env.SUPABASE_URL,
  process.env.SUPABASE_SERVICE_ROLE_KEY,
);

* url 과 key 가 자꾸 undefind 뜨길래 dotenv 로 억지로 읽어와 주었다. 

 

 

//image Controller 
import {
  Controller,
  Logger,
  Post,
  UploadedFiles,
  UseGuards,
  UseInterceptors,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { FilesInterceptor } from '@nestjs/platform-express';
import * as fs from 'fs';
import { supabase } from 'lib/supabase';
import * as multer from 'multer';
import { numbers } from 'src/@common/contants';
try {
  fs.readdirSync('uploads');
} catch (error) {
  fs.mkdirSync('uploads');
}

@Controller('images')
@UseGuards(AuthGuard())
export class ImageController {
  private logger = new Logger('PostController');

  @UseInterceptors(
    FilesInterceptor('images', numbers.MAX_IMAGE_COUNT, {
      storage: multer.memoryStorage(),
      // diskStorage({
      //   destination(req, file, cb) {
      //     cb(null, 'uploads/');
      //   },
      //   filename(req, file, cb) {
      //     const ext = extname(file.originalname);
      //     cb(null, basename(file.originalname, ext) + Date.now() + ext);
      //   },
      // }),
      limits: { fileSize: numbers.MAX_IAMGE_SIZE },
    }),
  )
  @Post('/')
  async uploadImages(@UploadedFiles() files: Express.Multer.File[]) {
    const urls: string[] = [];

    for (const file of files) {
      const filePath = `images/${Date.now()}-${file.originalname}`;

      const { error } = await supabase.storage
        .from('buddiStorage')
        .upload(filePath, file.buffer, {
          contentType: file.mimetype,
        });

      if (error) throw error;

      const { data } = supabase.storage
        .from('buddiStorage')
        .getPublicUrl(filePath);
      const url = data.publicUrl;
      urls.push(url);

      await supabase.from('image').insert({
        uri: url,
      });
    }

    this.logger.log('이미지 저장 완료', urls);

    return urls;
  }
}

 

diskStorage 대신에 memoryStorage() 를 넣어준다. 

buddiStorage 의 Public Url 을 불러와 적용해준다. 

그리고 Db 에 삽입한다. 

 

 

 

 

//imagepriviewList
import { supabase } from "@/lib/supabase";
import { ImageUri } from "@/types";
import React from "react";
import { Image, Pressable, ScrollView, StyleSheet } from "react-native";

interface ImagePreviewListProps {
  imageUris: ImageUri[];
}

function ImagePreviewList({ imageUris = [] }: ImagePreviewListProps) {
  return (
     <ScrollView
      horizontal
      showsHorizontalScrollIndicator={false}
      contentContainerStyle={styles.container}
    >
      {imageUris.map(({ uri }, index) => {
        supabase.storage.from("buddiStorage").getPublicUrl(uri);

        return (
          <Pressable
            key={uri + index}
            style={styles.imageContainer}
            onPress={() =>
              router.push({ pathname: "/image", params: { uri: uri } })
            }
          >
            <Image style={styles.image} source={{ uri: uri }} />
          </Pressable>
        );
      })}
    </ScrollView>
  );
}

 ...

export default ImagePreviewList;

 

그렇다면 이미지가 잘 저장되며 잘 뜬다. 

반응형
반응형

이번엔 배포를 위한 첫 단계를 LocalDB 에서 Supabase DB 를 연결해주는 작업을 할 것이다. 

 

일단, 가입 먼저 해주자. 깃허브 자동배포를 위해서 깃허브 연동을 해주었다. 

 

1. Supabase 가입 후 프로젝트 생성하기 

https://supabase.com/

 

Supabase | The Postgres Development Platform.

Build production-grade applications with a Postgres database, Authentication, instant APIs, Realtime, Functions, Storage and Vector embeddings. Start for free.

supabase.com

 

가입할 때, 어려운 건 없으니 과정은 생략한다. 

 

2. 검색하다보면 Table 생성을 많이 테스트하던데 이미 프로젝트에서 각 모듈의 엔티티를 작성했고, TypeOrm 의 역할로 자동으로 생성될 것이니 생략한다. 

 

 

 

3. DB URL 을 찾아준다. 이거 찾느라 좀 시간이 걸렸다. 

 

 

상단바에 Connect 클릭, ORMs 클릭한다. 

 

 

# Connect to Supabase via connection pooling
DATABASE_URL="postgresql://postgres.***:[YOUR-PASSWORD]@aws-1-ap-southeast-2.pooler.supabase.com:6543/postgres?pgbouncer=true"

 

이러한 형태로 나와있을 것이다. 여기서 PASSWORD 는 Supabase 의 비밀번호다. 

 

4. 이제 프로젝트에 연결해준다. .env 파일로 가자 

... 기존 ... 
... 
DATABASE_URL=postgresql://postgres.sm ...

 

기존에 있던 설정값은 그대로 둬도 된다. 아래줄만 추가하자. 

 

 

5. appl.module.ts 로 간다. 

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    TypeOrmModule.forRoot({
      type: 'postgres',
      // host: process.env.DB_HOST,
      url: process.env.DATABASE_URL,
      // port: 5432,
      // username: process.env.DB_USERNAME,
      // password: process.env.DB_PASSWORD,
      // database: process.env.DB_DATABASE,
      // entities: [__dirname + '/**/*.entity.{js,ts}'],
      synchronize: true,
      autoLoadEntities: true,
    }),

 

정말 딱 이렇게만 설정해줘야 한다. type, url, synchronize, autoLoadEntities 

 

 ! Error 

더보기

Error 1: getaddrinfo ENOTFOUND url at GetAddrInfoReqWrap.onlookupall [as oncomplete] (node:dns:122:26)

 

==> “DNS가 저걸 호스트 주소로 인식 못하고 있다” 

===> host: process.env.DATABASE_URL 로 작성했다. host 를 url 로 변경해줘야 한다. 

 

Error 2:  Retrying (1)... Error: getaddrinfo ENOTFOUND db.smnpmlbdnlqhwqrbfniw.supabase.co at GetAddrInfoReqWrap.onlookupall 

 

==> 도메인을 못 찾겠다

===> db.xxx.supabase.co:5432 IPv6 이슈 때문에 종종 로컬에서 붙지 않는다. Supabase도 pooler 권장 

====> 5432 대신 6543 사용 

 

6. 테이블 확인하기 

잘 설정이 되었다. 

7. 데이터 넣어보기 

어플을 실행하고 가입만 해주었다. 

 

 

 

이제, 다음 단계인 FireBase ->  Supabase 이전해보러 갈 것이다! 

반응형
반응형

이번에 On/Off 기능을 개발하면서 

0데이터를 숨기기 위해 ' ' 처리를 하였는데, 합계에선 숫자에 문자열인  ' ' 을 더하려니 NaN 에러가 떴다. 

 

🐔 기존 코드는 이렇다. 

gridComplete: function(){


	var 근태시간합계		=minDuty5y("#jqGridM1").jqGrid('getCol', '근태시간', false, 'sum');
	var 연장시간합계		=horDuty6y("#jqGridM1").jqGrid('getCol', '연장시간', false, 'sum');
	
    ... 


	jQuery("#jqGridM1").jqGrid('footerData', 'set', { tmState : "합계"
					, 근태시간	: 근태시간합계 
				    , 연장시간	: 연장시간합계 
		 .. .

 

이렇게 Col 을 가져와 'sum' 키워드를 주어 가져왔다. 

 

🧐 그래서 Number( ) 를 이용해서 강제로 변환시키려 했지만 실패, 

🧐 cellvalue 를 이용하서 합계 때만 다시 변화 시도했지만 gridComplete 안에선 cellValue 가 존재하지 않아 실패 (formatter 에만 존재) 

 

 

결국 제미니의 도움을 받기로 했다. 

Reduce 를 이용하라는 답변이 왔다. 

 

📌Reduce 

array 가 주어졌을 때, 배열 각 요소에 대해 원하는 처리를 한 후, 하나의 값으로 돌려준다. 

 

여기서 map 과 무슨 차이가 있을까 했는데, Map은  배열의 각 요소를 딱 하나씩 변형해서 새 배열 반환하는데, 항상 원소개수가 똑같은 배열을 반환해준다. 그러나 Reduce 는 배열 전체를 하나의 값으로 합쳐서 돌려준다. 

 

그러면 reduce 는 누산기와 같은 역할을 수행할 수 있다는 뜻이다. 

 

 

 

 

var 근태시간합계 = [ 근태시간 ].reduce(function(prev, curr) { var value = parseFloat(curr) || 0; return prev + value; }, 0);

 

이제 소스를 해석해보도록 하자. 

 

A 사원의 근태시간 8 시간이라고 하고, B 사원의 근태시간도 8 시간이라 하자. C 사원의 근태시간은 9 시간이라 하자. 

 

그렇다면 배열은 [8,8, 9] 이렇게 들어간다. 

 

초기값이 0 이므로, prev = 0, curr = 8 (index 0) 부터 시작한다. curr 를 parseFloat 으로 변환하여 수로 변환하고 arr 의 length 길이만큼 돌려 값을 누산한다. 

 

* initialValue 를 설정하면 index 가 0부터 시작, 아니라면 1부터 시작한다. 

 

🐔 바뀐코드 

gridComplete: function(){
		
	var $grid = jQuery("#jqGridM1");

	var 근태시간 = $grid.jqGrid('getCol', '근태시간', false); 
	var 연장시간 = $grid.jqGrid('getCol', '연장시간', false); 

	...
    
	var 근태시간합계 = [ 근태시간 ].reduce(function(prev, curr) { var value = parseFloat(curr) || 0; return prev + value; }, 0);
	var 연장시간합계 = [ 연장시간 ].reduce(function(prev, curr) { var value = parseFloat(curr) || 0; return prev + value; }, 0);
	
    ...
    
    jQuery("#jqGridM1").jqGrid('footerData', 'set', { tmState : "합계"
															
							, 근태시간	: 근태시간합계 
				            , 연장시간	: 연장시간합계

 

그러면 정상적으로 25가 찍힐 것이다. 

 

반응형
반응형

앱을 개발하다가 더이상 세세한 기능에 시간을 지체할 여유가 없어서 일단 배포 후에 지속적으로 고도화 작업을 하기로 결정했다. 

역시 맨땅에 헤딩 !! 

 

배포를 하려고 하니 서버가 막막해서 GPT 에게 물어봤다. 

 

  • : Expo에서 빌드 → APK/Bundle → Google Play Internal Test
  • 서버: NestJS 그대로 + 환경변수(.env)로 Supabase 연결
  • DB: Supabase Postgres
  • 지도: Google Maps API Key

 

일단, 앱 자체는 Expo 에서 빌드를 하며, 서버는 Supabase 를 이용하기로 하였다. 비용 측면에서 훨씬 효율적이기 때문이다. 

Firebase 에서 사용하는 기능을 대체재로 삼기로 한다. 

 

 

  • Firebase Auth → Supabase Auth (옵션, NestJS 그대로 Auth 처리해도 됨)
  • Firebase DB(Firestore) → 이미 PostgreSQL 있으니까 그대로 둬도 됨
  • Firebase Storage → Supabase Storage로 바꿀 수 있음 (이미지/파일 관리용)

NestJs 도 Supabase 로 바꾸면 거의 0원에 도달한다는데,,, 이미 만든게 아까우니 그대로 사용하기로 하였다.

어차피 사용 유저도 없고 .. ㅎㅎ 

 

유지한다면? 

앱 → /posts → NestJS → DB 조회 → 응답

 

Supabase 로 바꾼다면? 

앱 → Supabase .from('posts').select('*') 

 

항목예상 비용 ( 테스트용 + 최대 100명 제한 시에) 

NestJS 서버 Render / Railway / Fly.io 소규모 인스턴스 1~3만원 서버 유지, API 처리용
DB Supabase Postgres 무료 플랜 0원 500MB, 50,000 MAU 충분
Auth Supabase Auth 0원 무료, JWT 발급, 이메일/소셜 로그인 지원
Storage Supabase Storage (이미지/파일) 0~1만원 무료 1GB 포함, 이미지 많으면 유료
Google Maps API Maps + Places 0원 무료 $200 크레딧 내 충분
Google Play 등록 1회 $25 (~3만원) 개발자 등록비, 한 번만 결제

 

 

💸 월별 비용:2~4만원 수준  

 

무료 서버 있는 지 물어봤다 (무슨 허니버터칩마냥 숨겨놓음...) 

 

Render Free Tier NodeJS, Docker 배포 가능 750h/월, 512MB RAM
Railway Free Tier NodeJS, Postgres, 간편 배포 500h/월, 512MB RAM, 1GB DB
Fly.io Free Tier 글로벌 배포 가능 3GB RAM-Month, 160GB-hr/월
Supabase Edge Functions 서버리스, JS/TS 실행 가능 125,000 요청/월 무료

 

 

 

오키오키 이걸로 가보자.. 

 

두근두근 다음부터 진짜 배포의 길을 걷는 것이다... !! 

반응형