diff --git a/.github/workflows/dev_deploy.yml b/.github/workflows/dev_deploy.yml index ff89465..6e5e5fc 100644 --- a/.github/workflows/dev_deploy.yml +++ b/.github/workflows/dev_deploy.yml @@ -1,86 +1,86 @@ name: Teammate Dev CI/CD on: - pull_request: # pull request -> merge 가 되었을 때 Github Action 실행! - types: [closed] - workflow_dispatch: # 수동 실행도 가능하도록 함 + pull_request: # pull request -> merge 가 되었을 때 Github Action 실행! + types: [closed] + workflow_dispatch: # 수동 실행도 가능하도록 함 jobs: - build: - # pull 요청이 dev에 merge 되었을 때 아래 steps를 실행 - if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'develop' + build: + # pull 요청이 dev에 merge 되었을 때 아래 steps를 실행 + if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'develop' - runs-on: ubuntu-latest # 우분투 최신 버전으로 실행 + runs-on: ubuntu-latest # 우분투 최신 버전으로 실행 - strategy: - matrix: - node-version: ["18.x"] # 노드 버전 지정! 여러 개도 가능! ['18.x', '14.x'] 요렇게 + strategy: + matrix: + node-version: ["20.x"] # 노드 버전 지정! 여러 개도 가능! ['18.x', '14.x'] 요렇게 - steps: - # build 할 코드를 가져옴 (코드 checkout - github에서 제공해주는 checkout@v3 사용) - - name: Checkout - uses: actions/checkout@v3 + steps: + # build 할 코드를 가져옴 (코드 checkout - github에서 제공해주는 checkout@v3 사용) + - name: Checkout + uses: actions/checkout@v3 - # Node.js 세팅 - - name: Set up Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }} - cache: "npm" + # Node.js 세팅 + - name: Set up Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: "npm" - - name: Install Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }} + - name: Install Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} - # dependencies 설치, test and build - - name: Install dependencies - run: npm ci # dependencies 설치 npm ci, npm install 모두 다 됨! + # dependencies 설치, test and build + - name: Install dependencies + run: npm ci # dependencies 설치 npm ci, npm install 모두 다 됨! - - name: Run Build - run: npm run build # 빌드 + - name: Run Build + run: npm run build # 빌드 - # 배포 패키지 생성 - - name: Generate deployment package - run: | - cp -R .platform dist - cp package.json dist/package.json - cp package-lock.json dist/package-lock.json - cp Procfile dist/Procfile - cd dist - zip -r deploy.zip . + # 배포 패키지 생성 + - name: Generate deployment package + run: | + cp -R .platform dist + cp package.json dist/package.json + cp package-lock.json dist/package-lock.json + cp Procfile dist/Procfile + cd dist + zip -r deploy.zip . - # 생성한 deploy.zip 파일 내부 확인용! - - name: Get Zip Inside - run: zipinfo -1 dist/deploy.zip + # 생성한 deploy.zip 파일 내부 확인용! + - name: Get Zip Inside + run: zipinfo -1 dist/deploy.zip - # 현재 시간 얻기 (Build 시점의 시간 얻기) - - name: Get current time - uses: 1466587594/get-current-time@v2 - id: current-time - with: - format: YYYY-MM-DDTHH-mm-ss - utcOffset: "+09:00" # 한국 시간 고려 + # 현재 시간 얻기 (Build 시점의 시간 얻기) + - name: Get current time + uses: 1466587594/get-current-time@v2 + id: current-time + with: + format: YYYY-MM-DDTHH-mm-ss + utcOffset: "+09:00" # 한국 시간 고려 - # 현재 시간 출력 (위에서 얻은 build 시점의 시간 보여주기) - - name: Show Current Time - run: echo "CurrentTime=${{steps.current-time.outputs.formattedTime}}" - shell: bash + # 현재 시간 출력 (위에서 얻은 build 시점의 시간 보여주기) + - name: Show Current Time + run: echo "CurrentTime=${{steps.current-time.outputs.formattedTime}}" + shell: bash - # Beanstalk 배포 - - name: Beanstalk Deploy - uses: einaregilsson/beanstalk-deploy@v21 - with: - aws_access_key: ${{secrets.AWS_ACTION_ACCESS_KEY_ID}} - aws_secret_key: ${{secrets.AWS_ACTION_SECRET_ACCESS_KEY}} - application_name: teammate-dev - environment_name: Teammate-dev-env - version_label: github-action-${{ steps.current-time.outputs.formattedTime }} # version_label은 이전에 배포한 label과 중복되면 안됨! - use_existing_version_if_available: true - region: ap-northeast-2 - deployment_package: dist/deploy.zip - wait_for_deployment: false # 바로 Beanstalk으로 넘어갈 수 있도록 함 + # Beanstalk 배포 + - name: Beanstalk Deploy + uses: einaregilsson/beanstalk-deploy@v21 + with: + aws_access_key: ${{secrets.AWS_ACTION_ACCESS_KEY_ID}} + aws_secret_key: ${{secrets.AWS_ACTION_SECRET_ACCESS_KEY}} + application_name: teammate-dev + environment_name: Teammate-dev-env + version_label: github-action-${{ steps.current-time.outputs.formattedTime }} # version_label은 이전에 배포한 label과 중복되면 안됨! + use_existing_version_if_available: true + region: ap-northeast-2 + deployment_package: dist/deploy.zip + wait_for_deployment: false # 바로 Beanstalk으로 넘어갈 수 있도록 함 - # 그냥 다 했다고 출력하기 - - name: Deployed! - run: echo App deployed to ELB + # 그냥 다 했다고 출력하기 + - name: Deployed! + run: echo App deployed to ELB diff --git a/Procfile b/Procfile index 6daab30..fc9c553 100644 --- a/Procfile +++ b/Procfile @@ -1 +1,2 @@ -web: npm ci & npm run start +# web: npm ci & npm run start +web: npm ci && npm run start diff --git a/package.json b/package.json index 9482d36..0960a80 100644 --- a/package.json +++ b/package.json @@ -1,47 +1,47 @@ { - "name": "teammate-backend", - "version": "0.0.1", - "description": "", - "main": "app.ts", - "scripts": { - "start": "node main.js", - "test": "echo \"Error: no test specified\" && exit 1", - "start:dev": "nodemon --exec ts-node src/app.ts", - "start:dist": "webpack --mode production & node ./dist/main.js", - "build": "webpack --mode production" - }, - "author": "", - "license": "ISC", - "dependencies": { - "cors": "^2.8.5", - "dotenv": "^16.3.1", - "express": "^4.18.2", - "express-async-handler": "^1.2.0", - "http-status-codes": "^2.3.0", - "jsonwebtoken": "^9.0.2", - "mysql2": "^3.7.0", - "redaxios": "^0.5.1", - "sequelize": "^6.35.2", - "sequelize-cli": "^6.6.2", - "ts-loader": "^9.5.1", - "uuid": "^9.0.1", - "zod": "^3.22.4" - }, - "devDependencies": { - "@types/cors": "^2.8.17", - "@types/express": "^4.17.21", - "@types/jsonwebtoken": "^9.0.5", - "@types/node": "^20.11.0", - "@typescript-eslint/eslint-plugin": "^6.18.1", - "@typescript-eslint/parser": "^6.18.1", - "eslint": "^8.56.0", - "eslint-config-prettier": "^9.1.0", - "nodemon": "^3.0.2", - "prettier": "^3.1.1", - "ts-node": "^10.9.2", - "typescript": "^5.3.3", - "webpack": "^5.90.2", - "webpack-cli": "^5.1.4", - "webpack-node-externals": "^3.0.0" - } + "name": "teammate-backend", + "version": "0.0.1", + "description": "", + "main": "app.ts", + "scripts": { + "start": "node dist/main.js", + "test": "echo \"Error: no test specified\" && exit 1", + "start:dev": "nodemon --exec ts-node src/app.ts", + "start:dist": "webpack --mode production & node ./dist/main.js", + "build": "webpack --mode production" + }, + "author": "", + "license": "ISC", + "dependencies": { + "cors": "^2.8.5", + "dotenv": "^16.3.1", + "express": "^4.18.2", + "express-async-handler": "^1.2.0", + "http-status-codes": "^2.3.0", + "jsonwebtoken": "^9.0.2", + "mysql2": "^3.7.0", + "redaxios": "^0.5.1", + "sequelize": "^6.35.2", + "sequelize-cli": "^6.6.2", + "ts-loader": "^9.5.1", + "uuid": "^9.0.1", + "zod": "^3.22.4" + }, + "devDependencies": { + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/jsonwebtoken": "^9.0.5", + "@types/node": "^20.11.0", + "@typescript-eslint/eslint-plugin": "^6.18.1", + "@typescript-eslint/parser": "^6.18.1", + "eslint": "^8.56.0", + "eslint-config-prettier": "^9.1.0", + "nodemon": "^3.0.2", + "prettier": "^3.1.1", + "ts-node": "^10.9.2", + "typescript": "^5.3.3", + "webpack": "^5.90.2", + "webpack-cli": "^5.1.4", + "webpack-node-externals": "^3.0.0" + } } diff --git a/src/app.ts b/src/app.ts index 8fd6504..5440e16 100644 --- a/src/app.ts +++ b/src/app.ts @@ -55,7 +55,6 @@ app.use((req: Request, res: Response, next: NextFunction) => { app.use((err, req: Request, res: Response, next: NextFunction) => { res.locals.message = err.message; res.locals.err = process.env.NODE_ENV !== "production" ? err : {}; - console.log(err); const error = err instanceof BaseError ? err : new BaseError(status.INTERNAL_SERVER_ERROR); res.status(error.data.status).send(response(error.data)); }); diff --git a/src/config/config.ts b/src/config/config.ts index 5d70b2c..c1b0cca 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -3,7 +3,7 @@ dotenv.config(); export const config = { development: { - username: process.env.DB_USERNAME || "root", + username: process.env.DB_USER_NAME || "root", password: process.env.DB_PASSWORD, database: process.env.DB_NAME || "database_development", host: process.env.DB_HOST || "127.0.0.1", @@ -17,10 +17,10 @@ export const config = { dialect: "mysql", }, production: { - username: "root", - password: null, - database: "database_production", - host: "127.0.0.1", + username: process.env.DB_USER_NAME || "root", + password: process.env.DB_PASSWORD, + database: process.env.DB_NAME || "database_production", + host: process.env.DB_HOST || "127.0.0.1", dialect: "mysql", }, }; diff --git a/src/controllers/posts.controller.ts b/src/controllers/posts.controller.ts index bc13a99..725da32 100644 --- a/src/controllers/posts.controller.ts +++ b/src/controllers/posts.controller.ts @@ -6,20 +6,25 @@ import { readBookmarkedPosts, readPost, readCommunityPosts, - readMyPosts, + readMyCommunityPosts, createCommunityPost, createComment, readComments, createRentPost, readRentPosts, + readMyRentPosts, } from "../services/posts.service"; export const fetchCommunityPosts = async (req, res: Response, next) => { res.send(response(status.SUCCESS, await readCommunityPosts(req.user?.id, req.query))); }; -export const fetchMyPosts = async (req, res: Response, next) => { - res.send(response(status.SUCCESS, await readMyPosts(req.user.id, req.query))); +export const fetchMyCommunityPosts = async (req, res: Response, next) => { + res.send(response(status.SUCCESS, await readMyCommunityPosts(req.user.id, req.query))); +}; + +export const fetchMyRentPosts = async (req, res: Response, next) => { + res.send(response(status.SUCCESS, await readMyRentPosts(req.user.id, req.query))); }; export const fetchBookmarkedPosts = async (req, res: Response, next) => { diff --git a/src/daos/game.dao.ts b/src/daos/game.dao.ts index 1aec4d3..82ba41a 100644 --- a/src/daos/game.dao.ts +++ b/src/daos/game.dao.ts @@ -29,7 +29,8 @@ export const findGamesByGender = async ( export const findGamesByLevel = async (date: string, category: Category, skillLevel, cursorId: number | undefined) => { const gamesBeforeCursor = generateCursorCondition(cursorId); - const teamFilter = { category, skillLevel }; + const minLevel = Math.floor(parseInt(skillLevel) / 10) * 10; + const teamFilter = { skillLevel: { [Op.between]: [minLevel, minLevel + 9] }, category }; return findGames(date, gamesBeforeCursor, teamFilter); }; diff --git a/src/daos/image.dao.ts b/src/daos/image.dao.ts index 03abdff..8e94934 100644 --- a/src/daos/image.dao.ts +++ b/src/daos/image.dao.ts @@ -5,6 +5,6 @@ export const findImage = async (postId: number) => { where: { postId, }, - attributes: ["url"], + attributes: ["link"], }); }; diff --git a/src/daos/post.dao.ts b/src/daos/post.dao.ts index 2b2da0a..b2c12d9 100644 --- a/src/daos/post.dao.ts +++ b/src/daos/post.dao.ts @@ -21,8 +21,8 @@ export const findPostByType = async ( } }; -export const findPostByAuthorId = async (userId: number, cursorId?: number) => { - const postsBeforeCursorForAuthor = { authorId: userId, ...generateCursorCondition(cursorId) }; +export const findPostByAuthorId = async (userId: number, type: PostType, cursorId?: number) => { + const postsBeforeCursorForAuthor = { authorId: userId, type, ...generateCursorCondition(cursorId) }; return findPostByFilter(userId, postsBeforeCursorForAuthor); }; @@ -46,7 +46,7 @@ export const getPost = async (postId: number) => { where: { id: postId, }, - attributes: ["title", "content", "link"], + attributes: ["title", "content", "link", "rentMapValue"], }); }; diff --git a/src/daos/team.dao.ts b/src/daos/team.dao.ts index a572dc8..1db9e8a 100644 --- a/src/daos/team.dao.ts +++ b/src/daos/team.dao.ts @@ -160,11 +160,11 @@ export const findTeamIdByLeaderId = async (userId: number) => { return teams.map((team) => team.id); }; -export const getTeamCategoryByLeaderId = async (userId: number) => { +export const getTeamCategoryById = async (teamId: number) => { const team = await db.Team.findOne({ raw: true, where: { - leaderId: userId, + id: teamId, }, attributes: ["category"], }); diff --git a/src/dtos/posts.dto.ts b/src/dtos/posts.dto.ts index d9b40f7..82ab6a9 100644 --- a/src/dtos/posts.dto.ts +++ b/src/dtos/posts.dto.ts @@ -22,13 +22,14 @@ export const readRentPostsResponseDTO = (result) => { }; }; -export const readPostResponseDTO = (post, imageUrls, commentCount, comments, isBookmarked) => { +export const readPostResponseDTO = (post, commentCount, comments, isBookmarked) => { + console.log(post); return { post: { title: post.title, - contnet: post.content, - link: post.link, - imageUrls: imageUrls, + content: post.content, + imageUrls: post.link, + mapCoordinateVal: post.rentMapValue, }, isBookmarked, commentCount, diff --git a/src/models/index.ts b/src/models/index.ts index 44e73e0..4f5bfe9 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -1,7 +1,19 @@ import { Sequelize, Dialect } from "sequelize"; -import fs from "fs"; -import path from "path"; import { config as Config } from "../config/config"; +const Bookmark = require("./bookmark.model"); +const Comment = require("./comment.model"); +const GameApply = require("./game-apply.model"); +const Game = require("./game.model"); +const GuestUser = require("./guest-user.model"); +const Guest = require("./guest.model"); +const Image = require("./image.model"); +const Member = require("./member.model"); +const Post = require("./post.model"); +const Profile = require("./profile.model"); +const TeamReview = require("./team-review.model"); +const Team = require("./team.model"); +const UserReview = require("./user-review.model"); +const User = require("./user.model"); const env = process.env.NODE_ENV || "development"; const config = Config[env]; @@ -14,21 +26,49 @@ const sequelize = new Sequelize(config.database, config.username, config.passwor db.sequelize = sequelize; -const basename = path.basename(__filename); -fs.readdirSync(__dirname) - .filter((file) => { - return file.indexOf(".") !== 0 && file !== basename && file.slice(-3) === ".ts"; - }) - .forEach((file) => { - const model = require(path.join(__dirname, file)); - db[model.name] = model; - model.initiate(sequelize); - }); +db.Bookmark = Bookmark; +db.Comment = Comment; +db.GameApply = GameApply; +db.Game = Game; +db.GuestUser = GuestUser; +db.Guest = Guest; +db.Image = Image; +db.Member = Member; +db.Post = Post; +db.Profile = Profile; +db.TeamReview = TeamReview; +db.Team = Team; +db.UserReview = UserReview; +db.User = User; -Object.keys(db).forEach((modelName) => { - if (db[modelName].associate) { - db[modelName].associate(db); - } -}); +Bookmark.initiate(sequelize); +Comment.initiate(sequelize); +GameApply.initiate(sequelize); +Game.initiate(sequelize); +GuestUser.initiate(sequelize); +Guest.initiate(sequelize); +Image.initiate(sequelize); +Member.initiate(sequelize); +Post.initiate(sequelize); +Profile.initiate(sequelize); +TeamReview.initiate(sequelize); +Team.initiate(sequelize); +UserReview.initiate(sequelize); +User.initiate(sequelize); + +Bookmark.associate(db); +Comment.associate(db); +GameApply.associate(db); +Game.associate(db); +GuestUser.associate(db); +Guest.associate(db); +Image.associate(db); +Member.associate(db); +Post.associate(db); +Profile.associate(db); +TeamReview.associate(db); +Team.associate(db); +UserReview.associate(db); +User.associate(db); export default db; diff --git a/src/models/post.model.ts b/src/models/post.model.ts index fee9020..96aefcf 100644 --- a/src/models/post.model.ts +++ b/src/models/post.model.ts @@ -32,6 +32,10 @@ class Post extends Model, InferCreationAttributes> { type: new DataTypes.STRING(100), allowNull: true, }, + rentMapValue: { + type: new DataTypes.STRING(100), + allowNull: true, + }, rentStatus: { type: new DataTypes.INTEGER(), allowNull: true, diff --git a/src/routes/posts.route.ts b/src/routes/posts.route.ts index 57923fa..bf881fc 100644 --- a/src/routes/posts.route.ts +++ b/src/routes/posts.route.ts @@ -6,12 +6,13 @@ import { fetchBookmarkedPosts, fetchPost, fetchCommunityPosts, - fetchMyPosts, + fetchMyCommunityPosts, addCommunityPost, addComment, fetchComments, addRentPost, fetchRentPosts, + fetchMyRentPosts, } from "../controllers/posts.controller"; import { validate } from "../middlewares/validate.middleware"; import { createPostSchema } from "../schemas/post.schema"; @@ -23,7 +24,9 @@ postsRouter.post("/community", verifyUser, validate(createPostSchema), asyncHand postsRouter.get("/community", verifyUserIfExists, asyncHandler(fetchCommunityPosts)); -postsRouter.get("/authors/me", verifyUser, asyncHandler(fetchMyPosts)); +postsRouter.get("/community/authors/me", verifyUser, asyncHandler(fetchMyCommunityPosts)); + +postsRouter.get("/rent/authors/me", verifyUser, asyncHandler(fetchMyRentPosts)); postsRouter.get("/bookmarks", verifyUser, asyncHandler(fetchBookmarkedPosts)); diff --git a/src/schemas/fields.ts b/src/schemas/fields.ts index 19142d3..c42b2c2 100644 --- a/src/schemas/fields.ts +++ b/src/schemas/fields.ts @@ -69,7 +69,7 @@ export const categoryParam = object({ }), }); -export const hostTeamIdField = { hostTeamId: z.number().int().optional() }; +export const hostTeamIdField = { hostTeamId: z.number().int() }; export const gameTimeField = { gameTime: z.preprocess((arg) => { diff --git a/src/schemas/game.schema.ts b/src/schemas/game.schema.ts index c6d0b1e..26b8a6e 100644 --- a/src/schemas/game.schema.ts +++ b/src/schemas/game.schema.ts @@ -12,6 +12,12 @@ import { } from "./fields"; const body = object({ + ...gameTimeField, + ...gameDurationField, + ...descriptionFieldInGame, +}); + +const createGameBody = object({ ...hostTeamIdField, ...gameTimeField, ...gameDurationField, @@ -19,10 +25,10 @@ const body = object({ }); export const createGameSchema = object({ - body: body, + body: createGameBody, }); -export type CreateGameBody = TypeOf; +export type CreateGameBody = TypeOf; export const updateGameSchema = object({ body: body, diff --git a/src/services/games.service.ts b/src/services/games.service.ts index 93b2b51..2457951 100644 --- a/src/services/games.service.ts +++ b/src/services/games.service.ts @@ -10,13 +10,7 @@ import { setGame, insertGameApplication, } from "../daos/game.dao"; -import { - getTeam, - getTeamDetailForGuesting, - getTeamIdByLeaderId, - getTeamCategoryByLeaderId, - getTeamByLeaderId, -} from "../daos/team.dao"; +import { getTeam, getTeamDetailForGuesting, getTeamCategoryById, getTeamByLeaderId } from "../daos/team.dao"; import { addMemberCount, findMemberInfoByCategory } from "../daos/member.dao"; import { getUserInfoByCategory, userInfoAttributes } from "../daos/user.dao"; import { getGameByUserId } from "../daos/game.dao"; @@ -67,15 +61,14 @@ export const readGameDetail = async (params) => { }; export const createGame = async (userId, body: CreateGameBody) => { - const hostTeamId = await getTeamIdByLeaderId(userId); - const category = await getTeamCategoryByLeaderId(userId); - - const team = await getTeamByLeaderId(hostTeamId, userId); + const teamId = body.hostTeamId; + const team = await getTeamByLeaderId(teamId, userId); if (!team) { throw new BaseError(status.TEAM_LEADER_NOT_FOUND); } - await insertGame(hostTeamId, body, category); + const category = await getTeamCategoryById(teamId); + await insertGame(teamId, body, category); return; }; diff --git a/src/services/posts.service.ts b/src/services/posts.service.ts index 7c9df29..e415fce 100644 --- a/src/services/posts.service.ts +++ b/src/services/posts.service.ts @@ -19,8 +19,13 @@ export const readCommunityPosts = async (userId: number | undefined, query) => { return readPostsResponseDTO(result); }; -export const readMyPosts = async (userId: number, query) => { - const result = await findPostByAuthorId(userId, query.cursorId); +export const readMyCommunityPosts = async (userId: number, query) => { + const result = await findPostByAuthorId(userId, PostType.Community, query.cursorId); + return readPostsResponseDTO(result); +}; + +export const readMyRentPosts = async (userId: number, query) => { + const result = await findPostByAuthorId(userId, PostType.RentalInfo, query.cursorId); return readPostsResponseDTO(result); }; @@ -37,11 +42,10 @@ export const createOrDeleteBookmark = async (userId: number, params) => { export const readPost = async (userId: number, params) => { const postId = params.postId; const post = handlePostNotFound(await getPost(postId)); - const imageUrls = await findImage(postId); const commentCount = await getCommentCount(postId); const comments = await findComment(postId, undefined); const isBookmarked = await checkIsBookmarked(userId, postId); - return readPostResponseDTO(post, imageUrls, commentCount, comments, isBookmarked); + return readPostResponseDTO(post, commentCount, comments, isBookmarked); }; const checkIsBookmarked = async (userId: number | undefined, postId: number) => { diff --git a/webpack.config.js b/webpack.config.js index fb6e846..b40c576 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -3,30 +3,32 @@ const nodeExternals = require("webpack-node-externals"); const path = require("path"); module.exports = { - mode: "development", - context: __dirname + "/src", - entry: { - app: "./app.ts", - }, - output: { - path: path.resolve(__dirname, "dist"), - filename: "main.js", - }, - module: { - rules: [ - { - test: /\.ts$/, - use: "ts-loader", - exclude: /node_modules/, - }, - ], - }, - target: "node", - externalsPresets: { - node: true, - }, - externals: [nodeExternals()], - resolve: { - extensions: [".ts", ".js"], - }, + mode: "development", + context: __dirname + "/src", + entry: { + app: "./app.ts", + }, + output: { + path: path.resolve(__dirname, "dist"), + filename: "main.js", + }, + module: { + rules: [ + { + test: /\.ts$/, + use: { + loader: "ts-loader", + }, + exclude: /node_modules/, + }, + ], + }, + target: "node", + externalsPresets: { + node: true, + }, + externals: [nodeExternals()], + resolve: { + extensions: [".ts", ".js"], + }, };