fix : Stop bet is fixed partially
This commit is contained in:
parent
89009d28c4
commit
d1b52dc64c
@ -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) {
|
||||||
try {
|
console.log('[STOPBET] No Stopbet entry found in localStorage');
|
||||||
const parsed = JSON.parse(cached);
|
return;
|
||||||
if (parsed.venue === this.currentVenue && parsed.date === this.currentDate) {
|
}
|
||||||
this.stopbetStatuses.clear();
|
|
||||||
for (const [race, status] of Object.entries(parsed.statuses)) {
|
try {
|
||||||
const raceNum = parseInt(race, 10);
|
const parsed = JSON.parse(cached);
|
||||||
if (!isNaN(raceNum) && ['Y', 'N', 'S'].includes(status as string)) {
|
if (!parsed || typeof parsed !== 'object') {
|
||||||
this.stopbetStatuses.set(raceNum, status as string);
|
console.warn('[STOPBET] Stopbet entry has unexpected shape, ignoring.');
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
console.log('[STOPBET] Loaded statuses from localStorage:', this.stopbetStatuses);
|
|
||||||
this.stopbetStatusesSubject.next(new Map(this.stopbetStatuses));
|
// If stored venue/date match current ones, load statuses into memory
|
||||||
} else {
|
if (parsed.venue === this.currentVenue && parsed.date === this.currentDate && parsed.statuses) {
|
||||||
console.log(
|
this.stopbetStatuses.clear();
|
||||||
`[STOPBET] localStorage data invalid for venue=${this.currentVenue}, date=${this.currentDate}:`,
|
for (const [race, status] of Object.entries(parsed.statuses)) {
|
||||||
parsed
|
const raceNum = parseInt(race, 10);
|
||||||
);
|
const s = String(status).toUpperCase();
|
||||||
}
|
if (!isNaN(raceNum) && ['Y', 'N', 'S'].includes(s)) {
|
||||||
} catch (err) {
|
this.stopbetStatuses.set(raceNum, s);
|
||||||
console.error('[STOPBET] Failed to parse localStorage:', err);
|
}
|
||||||
localStorage.removeItem(this.STORAGE_KEY);
|
}
|
||||||
|
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() {
|
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) {
|
||||||
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.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) {
|
||||||
this.eventSource.close();
|
try {
|
||||||
|
this.eventSource.close();
|
||||||
|
} catch (e) {
|
||||||
|
/* ignore close errors */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user