File size: 4,346 Bytes
b2806e8
 
 
 
 
8626b2e
 
b2806e8
8626b2e
 
b2806e8
 
 
 
 
 
 
 
8626b2e
 
b2806e8
 
 
8626b2e
 
 
 
b2806e8
 
8626b2e
 
 
 
b2806e8
 
 
 
 
 
 
 
 
 
 
8626b2e
 
 
 
 
 
 
 
 
b2806e8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8626b2e
 
 
 
 
 
 
 
 
 
b2806e8
 
 
 
 
 
 
 
 
 
 
 
 
 
8626b2e
b2806e8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import {
  Controller,
  Get,
  Post,
  Param,
  Query,
  Body,
  Res,
  Inject,
  Optional,
  NotFoundException,
} from '@nestjs/common';
import { Response } from 'express';
import { MarketsService } from './markets.service';
import { MarketCacheService } from './market-cache.service';

@Controller('markets')
export class MarketsController {
  private readonly marketQueue: any;

  constructor(
    private readonly marketsService: MarketsService,
    private readonly cacheService: MarketCacheService,
    @Optional() @Inject('BullQueue_market-processing') marketQueue?: any,
  ) {
    this.marketQueue = marketQueue || null;
  }

  @Get()
  getMarkets(@Query('q') query?: string) {
    if (query && query.trim().length > 0) {
      return this.marketsService.searchMarkets(query);
    }
    const markets = this.marketsService.getMarkets();
    // Enrich with processing status
    return markets.map((m) => {
      const cached = this.cacheService.get(m.slug);
      return {
        ...m,
        processingStatus: cached?.status || 'unknown',
      };
    });
  }

  @Post('search')
  searchMarket(@Body('query') query: string) {
    if (!query || query.trim().length < 2) {
      return { error: 'Query must be at least 2 characters' };
    }
    const detail = this.marketsService.searchOrCreateMarket(query.trim());
    return detail;
  }

  @Get('jobs/status')
  getJobsStatus() {
    const all = this.cacheService.getAll();
    return {
      markets: all.map((c) => ({
        slug: c.slug,
        status: c.status,
        updatedAt: c.updatedAt,
        error: c.error,
      })),
      summary: {
        total: all.length,
        ready: all.filter((c) => c.status === 'ready').length,
        processing: all.filter((c) => c.status === 'processing').length,
        pending: all.filter((c) => c.status === 'pending').length,
        error: all.filter((c) => c.status === 'error').length,
      },
    };
  }

  @Post(':slug/refresh')
  async refreshMarket(@Param('slug') slug: string) {
    const market = this.marketsService.getMarkets().find((m) => m.slug === slug);
    if (!market) {
      throw new NotFoundException('Market not found');
    }
    if (this.marketQueue) {
      this.cacheService.setStatus(slug, 'pending');
      await this.marketQueue.add(
        'process-market',
        { slug },
        { removeOnComplete: 100, removeOnFail: 50 },
      );
      return { message: 'Market refresh queued', slug, status: 'pending' };
    }
    return { message: 'Market data refreshed (no queue)', slug, status: 'ready' };
  }

  @Get(':slug')
  getMarketDetail(@Param('slug') slug: string) {
    // Return cached data if available (processed by BullMQ worker)
    const cached = this.cacheService.get(slug);
    if (cached?.status === 'ready' && cached.data) {
      return {
        ...cached.data,
        fromCache: true,
        processingStatus: 'ready',
      };
    }

    // Try to get the market detail (works for both hardcoded + dynamic)
    const market = this.marketsService.getMarketDetail(slug);
    if (!market) {
      throw new NotFoundException('Market not found');
    }
    return {
      ...market,
      fromCache: false,
      processingStatus: cached?.status || 'not-queued',
    };
  }

  @Get(':slug/export/odds-csv')
  exportOddsCsv(@Param('slug') slug: string, @Res() res: Response) {
    const csv = this.marketsService.getOddsHistoryCsv(slug);
    if (!csv) {
      throw new NotFoundException('Market not found');
    }
    res.setHeader('Content-Type', 'text/csv');
    res.setHeader(
      'Content-Disposition',
      `attachment; filename=odds_history_${slug}.csv`,
    );
    res.send(csv);
  }

  @Get(':slug/export/integrity-csv')
  exportIntegrityCsv(@Param('slug') slug: string, @Res() res: Response) {
    const csv = this.marketsService.getIntegrityCsv(slug);
    if (!csv) {
      throw new NotFoundException('Market not found');
    }
    res.setHeader('Content-Type', 'text/csv');
    res.setHeader(
      'Content-Disposition',
      `attachment; filename=integrity_${slug}.csv`,
    );
    res.send(csv);
  }

  @Get(':slug/export/dossier-json')
  exportDossierJson(@Param('slug') slug: string) {
    const dossier = this.marketsService.getDossierJson(slug);
    if (!dossier) {
      throw new NotFoundException('Market not found');
    }
    return dossier;
  }
}