BasePlatformService.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. const Fastify = require('fastify');
  2. const RabbitMQConnector = require('./RabbitMQConnector');
  3. const { createLogger } = require('./logger');
  4. /**
  5. * BasePlatformService — tüm platform servisleri bu sınıftan extend eder.
  6. *
  7. * Her platform servisi şu metodları override etmeli:
  8. * - fetchFeed() → platform API'sinden feed çeker
  9. * - publishPost(post) → platforma içerik gönderir
  10. * - getStatus() → bağlantı durumu döner
  11. * - authenticate(code) → OAuth callback işler
  12. */
  13. class BasePlatformService extends RabbitMQConnector {
  14. constructor(platformName) {
  15. super();
  16. this.platformName = platformName;
  17. this.log = createLogger(platformName);
  18. this.app = Fastify({ logger: this.log });
  19. this._setupRoutes();
  20. }
  21. /** HTTP route'ları kaydet — platform servisleri override edebilir */
  22. _setupRoutes() {
  23. this.app.get('/status', async (request) => {
  24. const workspaceId = request.headers['x-workspace-id'] || 'default';
  25. return this.getStatus(workspaceId);
  26. });
  27. this.app.get('/feed', async (request, reply) => {
  28. const workspaceId = request.headers['x-workspace-id'] || 'default';
  29. try {
  30. const items = await this.fetchFeed({ ...request.query, workspaceId });
  31. return { success: true, platform: this.platformName, count: items.length, items };
  32. } catch (err) {
  33. const graphMsg = err.response?.data?.error?.message;
  34. const apiError = graphMsg || err.message;
  35. this.log.error({ action: 'fetch_feed', platform: this.platformName, outcome: 'failure', err: String(apiError) });
  36. reply.code(500).send({ success: false, error: String(apiError) });
  37. }
  38. });
  39. this.app.post('/post', async (request, reply) => {
  40. const workspaceId = request.headers['x-workspace-id'] || 'default';
  41. try {
  42. const result = await this.publishPost({ ...request.body, workspaceId });
  43. return { success: true, platform: this.platformName, result };
  44. } catch (err) {
  45. // Extract the actual third-party API error (e.g. Facebook Graph API error body)
  46. // rather than the generic axios "Request failed with status code 4xx" message.
  47. const graphMsg = err.response?.data?.error?.message;
  48. const graphCode = err.response?.data?.error?.code;
  49. const apiError = graphMsg
  50. ? (graphCode ? `[#${graphCode}] ${graphMsg}` : graphMsg)
  51. : (typeof err.response?.data?.error === 'string' ? err.response.data.error : err.message);
  52. this.log.error({ action: 'publish_post', platform: this.platformName, outcome: 'failure', err: String(apiError) });
  53. reply.code(500).send({ success: false, error: String(apiError) });
  54. }
  55. });
  56. this.app.get('/auth/callback', async (request, reply) => {
  57. try {
  58. const result = await this.authenticate(request.query);
  59. return { success: true, platform: this.platformName, result };
  60. } catch (err) {
  61. reply.code(500).send({ success: false, error: err.message });
  62. }
  63. });
  64. }
  65. /** HTTP sunucusunu başlat */
  66. async start(port = 3000) {
  67. await this.connect();
  68. await this.app.listen({ port, host: '0.0.0.0' });
  69. this.app.log.info({ action: 'service_start', port, outcome: 'success' }, `${this.platformName} service started`);
  70. }
  71. // ─── Alt sınıfların override edeceği metodlar ───────────────────────────────
  72. /** @returns {{ connected: boolean, platform: string, username?: string }} */
  73. async getStatus() {
  74. return { connected: false, platform: this.platformName };
  75. }
  76. /** @returns {Array<FeedItem>} normalize edilmiş feed öğeleri */
  77. async fetchFeed() {
  78. throw new Error(`[${this.platformName}] fetchFeed() implement edilmedi`);
  79. }
  80. /** @param {{ content: string, media?: Array, tags?: Array }} post */
  81. async publishPost() {
  82. throw new Error(`[${this.platformName}] publishPost() implement edilmedi`);
  83. }
  84. /** @param {{ code: string, state?: string }} query — OAuth callback params */
  85. async authenticate() {
  86. throw new Error(`[${this.platformName}] authenticate() implement edilmedi`);
  87. }
  88. // ─── Yardımcı metodlar ───────────────────────────────────────────────────────
  89. /**
  90. * Normalize edilmiş feed öğesi oluştur.
  91. * Platform servisleri bu şablonu kullanarak veriyi standartlaştırır.
  92. */
  93. normalizeFeedItem({
  94. originalId,
  95. author,
  96. content,
  97. contentHtml = null,
  98. media = [],
  99. links = [],
  100. platformTags = [],
  101. metrics = {},
  102. url = null,
  103. createdAt = new Date(),
  104. }) {
  105. return {
  106. platform: this.platformName,
  107. originalId: String(originalId),
  108. author: {
  109. name: author.name || '',
  110. username: author.username || '',
  111. avatar: author.avatar || null,
  112. profileUrl: author.profileUrl || null,
  113. },
  114. content,
  115. contentHtml,
  116. media,
  117. links,
  118. tags: [],
  119. platformTags,
  120. metrics: {
  121. likes: metrics.likes || 0,
  122. comments: metrics.comments || 0,
  123. shares: metrics.shares || 0,
  124. views: metrics.views || 0,
  125. bookmarks: metrics.bookmarks || 0,
  126. },
  127. url,
  128. createdAt: new Date(createdAt),
  129. fetchedAt: new Date(),
  130. };
  131. }
  132. }
  133. module.exports = BasePlatformService;