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 { HttpClient } from '@angular/common/http';
import { NgZone } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class StopbetService {
export class StopbetService implements OnDestroy {
private stopbetStatuses: Map<number, string> = new Map(); // raceNum => 'Y'|'N'|'S'
private stopbetStatusesSubject = new BehaviorSubject<Map<number, string>>(this.stopbetStatuses);
private currentVenue: string = '';
private currentDate: string = '';
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) {}
@ -20,48 +20,55 @@ export class StopbetService {
}
// Initialize stopbet for a venue and date
// NOTE: we DO NOT clear localStorage here — storage is preserved across refreshes.
initialize(venue: string, date: string) {
const newVenue = venue ? venue.toUpperCase() : '';
const newDate = date || this.getTodayDate();
// Only clear if venue and date are valid and different
if (newVenue && newDate && (this.currentVenue !== newVenue || this.currentDate !== newDate)) {
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 {
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.currentDate = newDate;
// Load from localStorage first
// Load from localStorage (Stopbet) if present
this.loadFromLocalStorage();
// Fetch from server and setup SSE
// Fetch from server and setup SSE (these will call setStatus which persists)
this.fetchInitialStopbets();
this.setupSSE();
}
// Public method to set race status
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);
if (oldStatus === status) {
console.log(`[STOPBET] No change for race ${raceNum}: status remains ${status}`);
if (oldStatus === normalizedStatus) {
console.log(`[STOPBET] No change for race ${raceNum}: status remains ${normalizedStatus}`);
return;
}
// Set the specified race status
this.stopbetStatuses.set(raceNum, status);
this.stopbetStatuses.set(raceNum, normalizedStatus);
// 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++) {
const prevStatus = this.stopbetStatuses.get(prevRace);
if (prevStatus === 'N' || prevStatus === undefined) {
@ -76,65 +83,105 @@ export class StopbetService {
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() {
const cached = localStorage.getItem(this.STORAGE_KEY);
if (cached) {
try {
const parsed = JSON.parse(cached);
if (parsed.venue === this.currentVenue && parsed.date === this.currentDate) {
this.stopbetStatuses.clear();
for (const [race, status] of Object.entries(parsed.statuses)) {
const raceNum = parseInt(race, 10);
if (!isNaN(raceNum) && ['Y', 'N', 'S'].includes(status as string)) {
this.stopbetStatuses.set(raceNum, status as string);
}
}
console.log('[STOPBET] Loaded statuses from localStorage:', this.stopbetStatuses);
this.stopbetStatusesSubject.next(new Map(this.stopbetStatuses));
} else {
console.log(
`[STOPBET] localStorage data invalid for venue=${this.currentVenue}, date=${this.currentDate}:`,
parsed
);
}
} catch (err) {
console.error('[STOPBET] Failed to parse localStorage:', err);
localStorage.removeItem(this.STORAGE_KEY);
if (!cached) {
console.log('[STOPBET] No Stopbet entry found in localStorage');
return;
}
try {
const parsed = JSON.parse(cached);
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();
for (const [race, status] of Object.entries(parsed.statuses)) {
const raceNum = parseInt(race, 10);
const s = String(status).toUpperCase();
if (!isNaN(raceNum) && ['Y', 'N', 'S'].includes(s)) {
this.stopbetStatuses.set(raceNum, s);
}
}
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));
} else {
// If the stored Stopbet is for a different venue/date, keep it in storage (do not remove).
console.log(
`[STOPBET] Stopbet in storage is for ${parsed.venue}:${parsed.date} — not loading into current ${this.currentVenue}:${this.currentDate}.`
);
}
} catch (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);
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() {
try {
const data = {
venue: this.currentVenue,
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));
console.log('[STOPBET] Saved statuses to localStorage:', data);
console.log('[STOPBET] Saved Stopbet to localStorage:', data);
} 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() {
if (!this.currentVenue || !this.currentDate) {
console.log('[STOPBET] fetchInitialStopbets skipped: venue/date not set');
return;
}
this.http
.get(`http://localhost:8080/stopbet/raw?venue=${this.currentVenue}&date=${this.currentDate}`)
.subscribe({
next: (res: any) => {
if (res.ok && res.data) {
if (res && res.ok && res.data) {
const key = `${this.currentVenue}:${this.currentDate}`;
const meeting = res.data[key] || {};
this.stopbetStatuses.clear();
// Use setStatus to ensure auto-stop logic and persistence run
for (const race in meeting) {
const raceNum = parseInt(race, 10);
const status = meeting[race];
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.saveToLocalStorage();
} else {
@ -147,16 +194,28 @@ export class StopbetService {
});
}
// Setup Server-Sent Events stream for real-time updates
private setupSSE() {
if (this.eventSource) {
this.eventSource.close();
try {
this.eventSource.close();
} catch (e) {
/* ignore close errors */
}
}
this.eventSource = new EventSource('http://localhost:8080/stopbet/stream');
try {
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.zone.run(() => {
try {
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);
this.setStatus(raceNum, data.status);
console.log('[STOPBET] SSE updated race', raceNum, 'to', data.status);
@ -166,17 +225,23 @@ export class StopbetService {
}
});
};
this.eventSource.onerror = (err) => {
console.error('[STOPBET] SSE error:', err);
};
}
// Returns true if betting is open for the given race
isOpen(race: number): boolean {
const status = this.stopbetStatuses.get(race);
return status === 'N' || status === undefined;
}
// Find open race starting from 'start' up to maxRaces, wrapping around
getOpenRaceStartingFrom(start: number, maxRaces: number): number {
if (!Number.isFinite(start) || !Number.isFinite(maxRaces) || maxRaces <= 0) {
return start;
}
for (let r = start; r <= maxRaces; r++) {
if (this.isOpen(r)) return r;
}
@ -196,7 +261,11 @@ export class StopbetService {
ngOnDestroy() {
if (this.eventSource) {
this.eventSource.close();
try {
this.eventSource.close();
} catch (e) {
/* ignore close errors */
}
}
}
}
}