fix : Stop bet is fixed partially

This commit is contained in:
Sibin Sabu 2025-09-17 14:24:21 +05:30
parent 89009d28c4
commit d1b52dc64c

View File

@ -1,16 +1,16 @@
import { Injectable } from '@angular/core'; import { Injectable, OnDestroy } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { NgZone } from '@angular/core'; import { NgZone } from '@angular/core';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class StopbetService { export class StopbetService implements OnDestroy {
private stopbetStatuses: Map<number, string> = new Map(); // raceNum => 'Y'|'N'|'S' private stopbetStatuses: Map<number, string> = new Map(); // raceNum => 'Y'|'N'|'S'
private stopbetStatusesSubject = new BehaviorSubject<Map<number, string>>(this.stopbetStatuses); private stopbetStatusesSubject = new BehaviorSubject<Map<number, string>>(this.stopbetStatuses);
private currentVenue: string = ''; private currentVenue: string = '';
private currentDate: string = ''; private currentDate: string = '';
private eventSource?: EventSource; private eventSource?: EventSource;
private readonly STORAGE_KEY = 'stopbetStatuses'; private readonly STORAGE_KEY = 'Stopbet'; // single key that persists across refreshes
constructor(private http: HttpClient, private zone: NgZone) {} constructor(private http: HttpClient, private zone: NgZone) {}
@ -20,48 +20,55 @@ export class StopbetService {
} }
// Initialize stopbet for a venue and date // Initialize stopbet for a venue and date
// NOTE: we DO NOT clear localStorage here — storage is preserved across refreshes.
initialize(venue: string, date: string) { initialize(venue: string, date: string) {
const newVenue = venue ? venue.toUpperCase() : ''; const newVenue = venue ? venue.toUpperCase() : '';
const newDate = date || this.getTodayDate(); const newDate = date || this.getTodayDate();
// Only clear if venue and date are valid and different
if (newVenue && newDate && (this.currentVenue !== newVenue || this.currentDate !== newDate)) { if (newVenue && newDate && (this.currentVenue !== newVenue || this.currentDate !== newDate)) {
console.log( console.log(
`[STOPBET] Venue or date changed (from ${this.currentVenue}:${this.currentDate} to ${newVenue}:${newDate}), clearing stopbetStatuses` `[STOPBET] Venue/date changed (from ${this.currentVenue}:${this.currentDate} to ${newVenue}:${newDate}). Keeping Stopbet storage intact until explicit logout.`
); );
this.stopbetStatuses.clear();
localStorage.removeItem(this.STORAGE_KEY);
this.stopbetStatusesSubject.next(new Map(this.stopbetStatuses));
} else { } else {
console.log( console.log(
`[STOPBET] No clear needed: newVenue=${newVenue}, newDate=${newDate}, currentVenue=${this.currentVenue}, currentDate=${this.currentDate}` `[STOPBET] initialize called: newVenue=${newVenue}, newDate=${newDate}, currentVenue=${this.currentVenue}, currentDate=${this.currentDate}`
); );
} }
this.currentVenue = newVenue; this.currentVenue = newVenue;
this.currentDate = newDate; this.currentDate = newDate;
// Load from localStorage first // Load from localStorage (Stopbet) if present
this.loadFromLocalStorage(); this.loadFromLocalStorage();
// Fetch from server and setup SSE // Fetch from server and setup SSE (these will call setStatus which persists)
this.fetchInitialStopbets(); this.fetchInitialStopbets();
this.setupSSE(); this.setupSSE();
} }
// Public method to set race status // Public method to set race status
setStatus(raceNum: number, status: string) { setStatus(raceNum: number, status: string) {
if (!Number.isFinite(raceNum) || raceNum <= 0) {
console.warn(`[STOPBET] Ignoring invalid raceNum: ${raceNum}`);
return;
}
const normalizedStatus = String(status).toUpperCase();
if (!['Y', 'N', 'S'].includes(normalizedStatus)) {
console.warn(`[STOPBET] Ignoring invalid status for race ${raceNum}: ${status}`);
return;
}
const oldStatus = this.stopbetStatuses.get(raceNum); const oldStatus = this.stopbetStatuses.get(raceNum);
if (oldStatus === status) { if (oldStatus === normalizedStatus) {
console.log(`[STOPBET] No change for race ${raceNum}: status remains ${status}`); console.log(`[STOPBET] No change for race ${raceNum}: status remains ${normalizedStatus}`);
return; return;
} }
// Set the specified race status // Set the specified race status
this.stopbetStatuses.set(raceNum, status); this.stopbetStatuses.set(raceNum, normalizedStatus);
// If stopping this race, auto-disable previous races // If stopping this race, auto-disable previous races
if (status === 'Y' || status === 'S') { if (normalizedStatus === 'Y' || normalizedStatus === 'S') {
for (let prevRace = 1; prevRace < raceNum; prevRace++) { for (let prevRace = 1; prevRace < raceNum; prevRace++) {
const prevStatus = this.stopbetStatuses.get(prevRace); const prevStatus = this.stopbetStatuses.get(prevRace);
if (prevStatus === 'N' || prevStatus === undefined) { if (prevStatus === 'N' || prevStatus === undefined) {
@ -76,65 +83,105 @@ export class StopbetService {
this.saveToLocalStorage(); this.saveToLocalStorage();
} }
// Load Stopbet entry from localStorage if it is parseable.
// We will only apply it if the stored venue/date match currentVenue/currentDate.
private loadFromLocalStorage() { private loadFromLocalStorage() {
const cached = localStorage.getItem(this.STORAGE_KEY); const cached = localStorage.getItem(this.STORAGE_KEY);
if (cached) { if (!cached) {
console.log('[STOPBET] No Stopbet entry found in localStorage');
return;
}
try { try {
const parsed = JSON.parse(cached); const parsed = JSON.parse(cached);
if (parsed.venue === this.currentVenue && parsed.date === this.currentDate) { if (!parsed || typeof parsed !== 'object') {
console.warn('[STOPBET] Stopbet entry has unexpected shape, ignoring.');
return;
}
// If stored venue/date match current ones, load statuses into memory
if (parsed.venue === this.currentVenue && parsed.date === this.currentDate && parsed.statuses) {
this.stopbetStatuses.clear(); this.stopbetStatuses.clear();
for (const [race, status] of Object.entries(parsed.statuses)) { for (const [race, status] of Object.entries(parsed.statuses)) {
const raceNum = parseInt(race, 10); const raceNum = parseInt(race, 10);
if (!isNaN(raceNum) && ['Y', 'N', 'S'].includes(status as string)) { const s = String(status).toUpperCase();
this.stopbetStatuses.set(raceNum, status as string); if (!isNaN(raceNum) && ['Y', 'N', 'S'].includes(s)) {
this.stopbetStatuses.set(raceNum, s);
} }
} }
console.log('[STOPBET] Loaded statuses from localStorage:', this.stopbetStatuses); console.log('[STOPBET] Loaded Stopbet from localStorage for current venue/date:', {
venue: parsed.venue,
date: parsed.date,
statuses: parsed.statuses,
});
this.stopbetStatusesSubject.next(new Map(this.stopbetStatuses)); this.stopbetStatusesSubject.next(new Map(this.stopbetStatuses));
} else { } else {
// If the stored Stopbet is for a different venue/date, keep it in storage (do not remove).
console.log( console.log(
`[STOPBET] localStorage data invalid for venue=${this.currentVenue}, date=${this.currentDate}:`, `[STOPBET] Stopbet in storage is for ${parsed.venue}:${parsed.date} — not loading into current ${this.currentVenue}:${this.currentDate}.`
parsed
); );
} }
} catch (err) { } catch (err) {
console.error('[STOPBET] Failed to parse localStorage:', err); console.error('[STOPBET] Failed to parse Stopbet from localStorage:', err);
// If corrupted, remove it to allow fresh writes next time
try {
localStorage.removeItem(this.STORAGE_KEY); localStorage.removeItem(this.STORAGE_KEY);
console.warn('[STOPBET] Removed corrupted Stopbet entry from localStorage.');
} catch (e) {
console.error('[STOPBET] Failed to remove corrupted Stopbet entry:', e);
} }
} else {
console.log('[STOPBET] No stopbetStatuses found in localStorage');
} }
} }
// Save the current statuses under Stopbet key (includes venue/date and a timestamp)
private saveToLocalStorage() { private saveToLocalStorage() {
try { try {
const data = { const data = {
venue: this.currentVenue, venue: this.currentVenue,
date: this.currentDate, date: this.currentDate,
statuses: Object.fromEntries(this.stopbetStatuses), statuses: Object.fromEntries(this.stopbetStatuses), // { "1":"Y", ... }
updatedAt: new Date().toISOString(),
}; };
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(data)); localStorage.setItem(this.STORAGE_KEY, JSON.stringify(data));
console.log('[STOPBET] Saved statuses to localStorage:', data); console.log('[STOPBET] Saved Stopbet to localStorage:', data);
} catch (err) { } catch (err) {
console.error('[STOPBET] Failed to save to localStorage:', err); console.error('[STOPBET] Failed to save Stopbet to localStorage:', err);
} }
} }
// Public helper to clear the Stopbet entry and reset in-memory statuses (call on logout)
public clearStopbetStorage() {
try {
localStorage.removeItem(this.STORAGE_KEY);
this.stopbetStatuses.clear();
this.stopbetStatusesSubject.next(new Map(this.stopbetStatuses));
console.log('[STOPBET] Cleared Stopbet storage and in-memory statuses (logout reset)');
} catch (err) {
console.error('[STOPBET] Failed to clear Stopbet storage:', err);
}
}
// Fetch initial statuses from backend and apply them
private fetchInitialStopbets() { private fetchInitialStopbets() {
if (!this.currentVenue || !this.currentDate) {
console.log('[STOPBET] fetchInitialStopbets skipped: venue/date not set');
return;
}
this.http this.http
.get(`http://localhost:8080/stopbet/raw?venue=${this.currentVenue}&date=${this.currentDate}`) .get(`http://localhost:8080/stopbet/raw?venue=${this.currentVenue}&date=${this.currentDate}`)
.subscribe({ .subscribe({
next: (res: any) => { next: (res: any) => {
if (res.ok && res.data) { if (res && res.ok && res.data) {
const key = `${this.currentVenue}:${this.currentDate}`; const key = `${this.currentVenue}:${this.currentDate}`;
const meeting = res.data[key] || {}; const meeting = res.data[key] || {};
this.stopbetStatuses.clear(); // Use setStatus to ensure auto-stop logic and persistence run
for (const race in meeting) { for (const race in meeting) {
const raceNum = parseInt(race, 10); const raceNum = parseInt(race, 10);
const status = meeting[race]; const status = meeting[race];
this.setStatus(raceNum, status); this.setStatus(raceNum, status);
} }
console.log('[STOPBET] Fetched initial statuses:', this.stopbetStatuses); console.log('[STOPBET] Fetched initial statuses and applied them.');
this.stopbetStatusesSubject.next(new Map(this.stopbetStatuses)); this.stopbetStatusesSubject.next(new Map(this.stopbetStatuses));
this.saveToLocalStorage(); this.saveToLocalStorage();
} else { } else {
@ -147,16 +194,28 @@ export class StopbetService {
}); });
} }
// Setup Server-Sent Events stream for real-time updates
private setupSSE() { private setupSSE() {
if (this.eventSource) { if (this.eventSource) {
try {
this.eventSource.close(); this.eventSource.close();
} catch (e) {
/* ignore close errors */
} }
}
try {
this.eventSource = new EventSource('http://localhost:8080/stopbet/stream'); this.eventSource = new EventSource('http://localhost:8080/stopbet/stream');
} catch (err) {
console.error('[STOPBET] Failed to create EventSource:', err);
return;
}
this.eventSource.onmessage = (event) => { this.eventSource.onmessage = (event) => {
this.zone.run(() => { this.zone.run(() => {
try { try {
const data = JSON.parse(event.data); const data = JSON.parse(event.data);
if (data.type === 'stopbet' && data.venue === this.currentVenue && data.date === this.currentDate) { if (data && data.type === 'stopbet' && data.venue === this.currentVenue && data.date === this.currentDate) {
const raceNum = parseInt(data.race, 10); const raceNum = parseInt(data.race, 10);
this.setStatus(raceNum, data.status); this.setStatus(raceNum, data.status);
console.log('[STOPBET] SSE updated race', raceNum, 'to', data.status); console.log('[STOPBET] SSE updated race', raceNum, 'to', data.status);
@ -166,17 +225,23 @@ export class StopbetService {
} }
}); });
}; };
this.eventSource.onerror = (err) => { this.eventSource.onerror = (err) => {
console.error('[STOPBET] SSE error:', err); console.error('[STOPBET] SSE error:', err);
}; };
} }
// Returns true if betting is open for the given race
isOpen(race: number): boolean { isOpen(race: number): boolean {
const status = this.stopbetStatuses.get(race); const status = this.stopbetStatuses.get(race);
return status === 'N' || status === undefined; return status === 'N' || status === undefined;
} }
// Find open race starting from 'start' up to maxRaces, wrapping around
getOpenRaceStartingFrom(start: number, maxRaces: number): number { getOpenRaceStartingFrom(start: number, maxRaces: number): number {
if (!Number.isFinite(start) || !Number.isFinite(maxRaces) || maxRaces <= 0) {
return start;
}
for (let r = start; r <= maxRaces; r++) { for (let r = start; r <= maxRaces; r++) {
if (this.isOpen(r)) return r; if (this.isOpen(r)) return r;
} }
@ -196,7 +261,11 @@ export class StopbetService {
ngOnDestroy() { ngOnDestroy() {
if (this.eventSource) { if (this.eventSource) {
try {
this.eventSource.close(); this.eventSource.close();
} catch (e) {
/* ignore close errors */
}
} }
} }
} }