Skip to content

Commit f88413e

Browse files
committed
Merge branch 'main' of https://github.com/boostcampwm-2024/web05-Denamu into chore/was-dev-compose
2 parents becc7f2 + 1ca0bdf commit f88413e

27 files changed

+411
-308
lines changed

client/docker/Dockerfile.local

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,26 @@ FROM node:22-alpine AS builder
33
WORKDIR /var/web05-Denamu/client
44

55
COPY ./client .
6+
ENV VITE_VISUALIZE=OFF
67

78
RUN npm ci
8-
99
RUN npm run build:local
1010

11-
FROM nginx:1.18.0
11+
RUN mkdir -p /etc/nginx/scripts
12+
COPY ../../nginx/scripts /etc/nginx/scripts
13+
RUN apk add --no-cache dos2unix
14+
RUN dos2unix /etc/nginx/scripts/generate_cert.sh
15+
16+
FROM nginx:stable-alpine
1217

1318
WORKDIR /var/web05-Denamu/client
1419

1520
COPY --from=builder /var/web05-Denamu/client/dist /var/web05-Denamu/client/dist
1621
COPY ../../static /var/web05-Denamu/static
1722

18-
COPY ../../nginx/scripts /etc/nginx/scripts
23+
COPY --from=builder /etc/nginx/scripts /etc/nginx/scripts
1924
COPY ../../nginx/nginx.conf /etc/nginx/nginx.conf
20-
21-
RUN apt-get update && apt-get install dos2unix
22-
RUN dos2unix /etc/nginx/scripts/generate_cert.sh
23-
RUN bash "/etc/nginx/scripts/generate_cert.sh"
25+
RUN apk add --no-cache openssl
26+
RUN sh /etc/nginx/scripts/generate_cert.sh
2427

2528
CMD ["nginx","-g","daemon off;"]

client/vite.config.ts

Lines changed: 56 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,56 @@
1-
import path from "path";
2-
import { visualizer } from "rollup-plugin-visualizer";
3-
import { defineConfig } from "vite";
4-
5-
import react from "@vitejs/plugin-react-swc";
6-
7-
// https://vite.dev/config/
8-
export default defineConfig({
9-
plugins: [
10-
react(),
11-
visualizer({
12-
open: true,
13-
brotliSize: true,
14-
}),
15-
],
16-
resolve: {
17-
alias: [{ find: "@", replacement: path.resolve(__dirname, "./src/") }],
18-
},
19-
build: {
20-
rollupOptions: {
21-
output: {
22-
manualChunks: {
23-
"radix-ui": [
24-
"@radix-ui/react-accordion",
25-
"@radix-ui/react-alert-dialog",
26-
"@radix-ui/react-avatar",
27-
"@radix-ui/react-dialog",
28-
"@radix-ui/react-dropdown-menu",
29-
"@radix-ui/react-label",
30-
"@radix-ui/react-navigation-menu",
31-
"@radix-ui/react-scroll-area",
32-
"@radix-ui/react-separator",
33-
"@radix-ui/react-slot",
34-
"@radix-ui/react-switch",
35-
"@radix-ui/react-tabs",
36-
"@radix-ui/react-toast",
37-
"@radix-ui/react-toggle",
38-
"@radix-ui/react-tooltip",
39-
],
40-
charts: ["recharts"],
41-
vendor: ["react", "react-dom", "react-router-dom"],
42-
animation: ["framer-motion"],
43-
query: ["@tanstack/react-query", "@tanstack/react-query-devtools"],
44-
socket: ["socket.io-client"],
45-
utils: ["class-variance-authority", "clsx", "tailwind-merge", "zustand"],
46-
"ui-utils": ["lucide-react", "avvvatars-react", "cmdk"],
47-
},
48-
},
49-
},
50-
},
51-
server: {
52-
watch: {
53-
usePolling: true,
54-
},
55-
},
56-
});
1+
import path from "path";
2+
import { visualizer } from "rollup-plugin-visualizer";
3+
import { defineConfig } from "vite";
4+
5+
import react from "@vitejs/plugin-react-swc";
6+
7+
// https://vite.dev/config/
8+
export default defineConfig({
9+
plugins: [
10+
react(),
11+
visualizer({
12+
open: !process.env.VITE_VISUALIZE,
13+
brotliSize: true,
14+
}),
15+
],
16+
resolve: {
17+
alias: [{ find: "@", replacement: path.resolve(__dirname, "./src/") }],
18+
},
19+
build: {
20+
rollupOptions: {
21+
output: {
22+
manualChunks: {
23+
"radix-ui": [
24+
"@radix-ui/react-accordion",
25+
"@radix-ui/react-alert-dialog",
26+
"@radix-ui/react-avatar",
27+
"@radix-ui/react-dialog",
28+
"@radix-ui/react-dropdown-menu",
29+
"@radix-ui/react-label",
30+
"@radix-ui/react-navigation-menu",
31+
"@radix-ui/react-scroll-area",
32+
"@radix-ui/react-separator",
33+
"@radix-ui/react-slot",
34+
"@radix-ui/react-switch",
35+
"@radix-ui/react-tabs",
36+
"@radix-ui/react-toast",
37+
"@radix-ui/react-toggle",
38+
"@radix-ui/react-tooltip",
39+
],
40+
charts: ["recharts"],
41+
vendor: ["react", "react-dom", "react-router-dom"],
42+
animation: ["framer-motion"],
43+
query: ["@tanstack/react-query", "@tanstack/react-query-devtools"],
44+
socket: ["socket.io-client"],
45+
utils: ["class-variance-authority", "clsx", "tailwind-merge", "zustand"],
46+
"ui-utils": ["lucide-react", "avvvatars-react", "cmdk"],
47+
},
48+
},
49+
},
50+
},
51+
server: {
52+
watch: {
53+
usePolling: true,
54+
},
55+
},
56+
});

docker-compose/docker-compose.dev.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ services:
2020
- ../nginx/logs:/var/log/nginx
2121
- ../nginx/nginx.conf:/etc/nginx/nginx.conf
2222
- ../static:/var/web05-Denamu/static
23+
- ../objects:/var/web05-Denamu/objects
2324
develop:
2425
watch:
2526
- path: ../nginx/nginx.conf

feed-crawler/.prettierrc

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
{
2-
"singleQuote": true,
3-
"trailingComma": "all"
4-
}
1+
{
2+
"singleQuote": true,
3+
"trailingComma": "all",
4+
"endOfLine": "lf"
5+
}

feed-crawler/src/common/constant.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export const CONNECTION_LIMIT = 50;
22
export const redisConstant = {
33
FEED_RECENT_ALL_KEY: 'feed:recent:*',
44
FEED_AI_QUEUE: `feed:ai:queue`,
5+
FULL_FEED_CRAWL_QUEUE: `feed:full-crawl:queue`,
56
};
67

78
export const ONE_MINUTE = 60 * 1000;

feed-crawler/src/common/parser/base-feed-parser.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ export abstract class BaseFeedParser {
4343
return detailedFeeds;
4444
}
4545

46+
async parseAllFeeds(rssObj: RssObj, xmlData: string): Promise<FeedDetail[]> {
47+
const rawFeeds = this.extractRawFeeds(xmlData);
48+
const detailedFeeds = await this.convertToFeedDetails(rssObj, rawFeeds);
49+
50+
return detailedFeeds;
51+
}
52+
4653
abstract canParse(xmlData: string): boolean;
4754
protected abstract extractRawFeeds(xmlData: string): RawFeed[];
4855

feed-crawler/src/common/parser/feed-parser-manager.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export class FeedParserManager {
3434

3535
const parser = this.findSuitableParser(xmlData);
3636
if (!parser) {
37-
throw new Error(`지원하지 않는 피드 형식: ${rssObj.rssUrl} / `);
37+
throw new Error(`지원하지 않는 피드 형식: ${rssObj.rssUrl}`);
3838
}
3939

4040
return await parser.parseFeed(rssObj, xmlData, startTime);
@@ -44,6 +44,36 @@ export class FeedParserManager {
4444
}
4545
}
4646

47+
async fetchAndParseAll(rssObj: RssObj): Promise<FeedDetail[]> {
48+
try {
49+
const response = await fetch(rssObj.rssUrl, {
50+
headers: {
51+
Accept:
52+
'application/rss+xml, application/xml, text/xml, application/atom+xml',
53+
},
54+
});
55+
56+
if (!response.ok) {
57+
throw new Error(`${rssObj.rssUrl}에서 피드 데이터 가져오기 실패`);
58+
}
59+
60+
const xmlData = await response.text();
61+
62+
const parser = this.findSuitableParser(xmlData);
63+
if (!parser) {
64+
throw new Error(`지원하지 않는 피드 형식: ${rssObj.rssUrl}`);
65+
}
66+
logger.info(
67+
`${rssObj.blogName}: ${parser.constructor.name} 사용 (전체 피드)`,
68+
);
69+
70+
return await parser.parseAllFeeds(rssObj, xmlData);
71+
} catch (error) {
72+
logger.warn(`[${rssObj.rssUrl}] 전체 피드 파싱 중 오류 발생: ${error}`);
73+
return [];
74+
}
75+
}
76+
4777
private findSuitableParser(xmlData: string): BaseFeedParser | null {
4878
for (const parser of this.parsers) {
4979
if (parser.canParse(xmlData)) {

feed-crawler/src/common/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,9 @@ export type FeedAIQueueItem = {
4040
tagList?: string[];
4141
summary?: string;
4242
};
43+
44+
export interface FullFeedCrawlMessage {
45+
rssId: number;
46+
timestamp: number;
47+
deathCount: number;
48+
}

feed-crawler/src/container.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ import { FeedRepository } from './repository/feed.repository';
77
import { RedisConnection } from './common/redis-access';
88
import { TagMapRepository } from './repository/tag-map.repository';
99
import { ParserUtil } from './common/parser/utils/parser-util';
10-
import { ClaudeService } from './claude.service';
10+
import { ClaudeEventWorker } from './event_worker/workers/claude-event-worker';
1111
import { FeedParserManager } from './common/parser/feed-parser-manager';
1212
import { Rss20Parser } from './common/parser/formats/rss20-parser';
1313
import { Atom10Parser } from './common/parser/formats/atom10-parser';
1414
import { FeedCrawler } from './feed-crawler';
15+
import { FullFeedCrawlEventWorker } from './event_worker/workers/full-feed-crawl-event-worker';
1516

1617
container.registerSingleton<DatabaseConnection>(
1718
DEPENDENCY_SYMBOLS.DatabaseConnection,
@@ -38,9 +39,9 @@ container.registerSingleton<TagMapRepository>(
3839
TagMapRepository,
3940
);
4041

41-
container.registerSingleton<ClaudeService>(
42-
DEPENDENCY_SYMBOLS.ClaudeService,
43-
ClaudeService,
42+
container.registerSingleton<ClaudeEventWorker>(
43+
DEPENDENCY_SYMBOLS.ClaudeEventWorker,
44+
ClaudeEventWorker,
4445
);
4546

4647
container.registerSingleton<ParserUtil>(
@@ -68,4 +69,9 @@ container.registerSingleton<FeedCrawler>(
6869
FeedCrawler,
6970
);
7071

72+
container.registerSingleton<FullFeedCrawlEventWorker>(
73+
DEPENDENCY_SYMBOLS.FullFeedCrawlEventWorker,
74+
FullFeedCrawlEventWorker,
75+
);
76+
7177
export { container };
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { RedisConnection } from '../common/redis-access';
2+
import logger from '../common/logger';
3+
4+
export abstract class AbstractQueueWorker<T> {
5+
protected readonly nameTag: string;
6+
protected readonly redisConnection: RedisConnection;
7+
8+
constructor(nameTag: string, redisConnection: RedisConnection) {
9+
this.nameTag = nameTag;
10+
this.redisConnection = redisConnection;
11+
}
12+
13+
async start(): Promise<void> {
14+
logger.info(`========== ${this.nameTag} 작업 시작 ==========`);
15+
const startTime = Date.now();
16+
17+
try {
18+
await this.processQueue();
19+
} catch (error) {
20+
logger.error(`${this.nameTag} 처리 중 오류 발생: ${error.message}`);
21+
}
22+
23+
const endTime = Date.now();
24+
const executionTime = endTime - startTime;
25+
logger.info(`${this.nameTag} 실행 시간: ${executionTime / 1000}seconds`);
26+
logger.info(`========== ${this.nameTag} 작업 완료 ==========`);
27+
}
28+
29+
protected abstract processQueue(): Promise<void>;
30+
protected abstract getQueueKey(): string;
31+
protected abstract parseQueueMessage(message: string): T;
32+
protected abstract processItem(item: T): Promise<void>;
33+
34+
protected abstract handleFailure(item: T, error: Error): Promise<void>;
35+
}

0 commit comments

Comments
 (0)