feat: X 봇 프로필 정보 DB 저장 기능 추가
- x_profiles 테이블 생성 (username, display_name, avatar_url) - saveProfile 함수로 DB + Redis 캐시 동시 저장 - getProfile 함수 Redis → DB 폴백 지원 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
c5f5639b11
commit
149e85ebd9
2 changed files with 56 additions and 7 deletions
10
backend/sql/x_profiles.sql
Normal file
10
backend/sql/x_profiles.sql
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
-- X 프로필 테이블
|
||||||
|
CREATE TABLE IF NOT EXISTS x_profiles (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
username VARCHAR(50) NOT NULL UNIQUE,
|
||||||
|
display_name VARCHAR(100),
|
||||||
|
avatar_url TEXT,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_username (username)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
@ -20,11 +20,22 @@ async function xBotPlugin(fastify, opts) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* X 프로필 캐시 저장
|
* X 프로필 저장 (DB + Redis 캐시)
|
||||||
*/
|
*/
|
||||||
async function cacheProfile(username, profile) {
|
async function saveProfile(username, profile) {
|
||||||
if (!profile.displayName && !profile.avatarUrl) return;
|
if (!profile.displayName && !profile.avatarUrl) return;
|
||||||
|
|
||||||
|
// DB에 저장 (upsert)
|
||||||
|
await fastify.db.query(`
|
||||||
|
INSERT INTO x_profiles (username, display_name, avatar_url)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
display_name = VALUES(display_name),
|
||||||
|
avatar_url = VALUES(avatar_url),
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
`, [username, profile.displayName, profile.avatarUrl]);
|
||||||
|
|
||||||
|
// Redis 캐시에도 저장
|
||||||
const data = {
|
const data = {
|
||||||
username,
|
username,
|
||||||
displayName: profile.displayName,
|
displayName: profile.displayName,
|
||||||
|
|
@ -139,8 +150,8 @@ async function xBotPlugin(fastify, opts) {
|
||||||
async function syncNewTweets(bot) {
|
async function syncNewTweets(bot) {
|
||||||
const { tweets, profile } = await fetchTweets(bot.nitterUrl, bot.username);
|
const { tweets, profile } = await fetchTweets(bot.nitterUrl, bot.username);
|
||||||
|
|
||||||
// 프로필 캐시 업데이트
|
// 프로필 저장 (DB + 캐시)
|
||||||
await cacheProfile(bot.username, profile);
|
await saveProfile(bot.username, profile);
|
||||||
|
|
||||||
let addedCount = 0;
|
let addedCount = 0;
|
||||||
let ytAddedCount = 0;
|
let ytAddedCount = 0;
|
||||||
|
|
@ -178,11 +189,39 @@ async function xBotPlugin(fastify, opts) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* X 프로필 조회
|
* X 프로필 조회 (Redis 캐시 → DB)
|
||||||
*/
|
*/
|
||||||
async function getProfile(username) {
|
async function getProfile(username) {
|
||||||
const data = await fastify.redis.get(`${PROFILE_CACHE_PREFIX}${username}`);
|
// Redis 캐시 확인
|
||||||
return data ? JSON.parse(data) : null;
|
const cached = await fastify.redis.get(`${PROFILE_CACHE_PREFIX}${username}`);
|
||||||
|
if (cached) {
|
||||||
|
return JSON.parse(cached);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DB에서 조회
|
||||||
|
const [rows] = await fastify.db.query(
|
||||||
|
'SELECT username, display_name, avatar_url, updated_at FROM x_profiles WHERE username = ?',
|
||||||
|
[username]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (rows.length > 0) {
|
||||||
|
const row = rows[0];
|
||||||
|
const data = {
|
||||||
|
username: row.username,
|
||||||
|
displayName: row.display_name,
|
||||||
|
avatarUrl: row.avatar_url,
|
||||||
|
updatedAt: row.updated_at?.toISOString(),
|
||||||
|
};
|
||||||
|
// Redis 캐시에 저장
|
||||||
|
await fastify.redis.setex(
|
||||||
|
`${PROFILE_CACHE_PREFIX}${username}`,
|
||||||
|
PROFILE_TTL,
|
||||||
|
JSON.stringify(data)
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
fastify.decorate('xBot', {
|
fastify.decorate('xBot', {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue