1 Commits

Author SHA1 Message Date
861b3dc9d4 Refactor code structure and remove redundant sections for improved readability and maintainability
All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 1m48s
2026-04-10 17:44:37 -04:00
19 changed files with 52 additions and 2811 deletions

View File

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

1
.gitignore vendored
View File

@@ -5,3 +5,4 @@ resources/
frontend/vue-app/playwright-report/ frontend/vue-app/playwright-report/
frontend/vue-app/test-results/ frontend/vue-app/test-results/
backend/test-results/ backend/test-results/
.vscode/keybindings.json

View File

@@ -2,7 +2,7 @@
# file: config/version.py # file: config/version.py
import os import os
BASE_VERSION = "1.0.7" # update manually when releasing features BASE_VERSION = "1.0.8" # update manually when releasing features
def get_full_version() -> str: def get_full_version() -> str:
""" """

View File

@@ -1,144 +0,0 @@
{
"_default": {
"1": {
"id": "57c21328-637e-4df3-be5b-7f619cbf4076",
"created_at": 1771343995.1881204,
"updated_at": 1771343995.188121,
"name": "Take out trash",
"points": 20,
"is_good": true,
"image_id": "trash-can",
"user_id": null
},
"2": {
"id": "70316500-e4ce-4399-8e4b-86a4046fafcb",
"created_at": 1771343995.1881304,
"updated_at": 1771343995.1881304,
"name": "Make your bed",
"points": 25,
"is_good": true,
"image_id": "make-the-bed",
"user_id": null
},
"3": {
"id": "71afb2e5-18de-4f99-9e1e-2f4e391e6c2c",
"created_at": 1771343995.1881359,
"updated_at": 1771343995.1881359,
"name": "Sweep and clean kitchen",
"points": 15,
"is_good": true,
"image_id": "vacuum",
"user_id": null
},
"4": {
"id": "e0aae53d-d4b6-4203-b910-004917db6003",
"created_at": 1771343995.1881409,
"updated_at": 1771343995.188141,
"name": "Do homework early",
"points": 30,
"is_good": true,
"image_id": "homework",
"user_id": null
},
"5": {
"id": "0ba544f6-2d61-4009-af8f-bcb4e94b7a11",
"created_at": 1771343995.188146,
"updated_at": 1771343995.188146,
"name": "Be good for the day",
"points": 15,
"is_good": true,
"image_id": "good",
"user_id": null
},
"6": {
"id": "8b5750d4-5a58-40cb-a31b-667569069d34",
"created_at": 1771343995.1881511,
"updated_at": 1771343995.1881511,
"name": "Clean your mess",
"points": 20,
"is_good": true,
"image_id": "broom",
"user_id": null
},
"7": {
"id": "aec5fb49-06d0-43c4-aa09-9583064b7275",
"created_at": 1771343995.1881557,
"updated_at": 1771343995.1881557,
"name": "Fighting",
"points": 10,
"is_good": false,
"image_id": "fighting",
"user_id": null
},
"8": {
"id": "0221ab72-c6c0-429f-a5f1-bc3d843fce9e",
"created_at": 1771343995.1881602,
"updated_at": 1771343995.1881602,
"name": "Yelling at parents",
"points": 10,
"is_good": false,
"image_id": "yelling",
"user_id": null
},
"9": {
"id": "672bfc74-4b85-4e8e-a2d0-74f14ab966cc",
"created_at": 1771343995.1881647,
"updated_at": 1771343995.1881647,
"name": "Lying",
"points": 10,
"is_good": false,
"image_id": "lying",
"user_id": null
},
"10": {
"id": "d8cc254f-922b-4dc2-ac4c-32fc3bbda584",
"created_at": 1771343995.1881692,
"updated_at": 1771343995.1881695,
"name": "Not doing what told",
"points": 5,
"is_good": false,
"image_id": "ignore",
"user_id": null
},
"11": {
"id": "8be18d9a-48e6-402b-a0ba-630a2d50e325",
"created_at": 1771343995.188174,
"updated_at": 1771343995.188174,
"name": "Not flushing toilet",
"points": 5,
"is_good": false,
"image_id": "toilet",
"user_id": null
},
"12": {
"id": "b3b44115-529b-4eb3-9f8b-686dd24547a1",
"created_at": 1771345063.4665146,
"updated_at": 1771345063.4665148,
"name": "Take out trash",
"points": 21,
"is_good": true,
"image_id": "trash-can",
"user_id": "a5f05d38-7f7c-4663-b00f-3d6138e0e246"
},
"13": {
"id": "c74fc8c7-5af1-4d40-afbb-6da2647ca18b",
"created_at": 1771345069.1633172,
"updated_at": 1771345069.1633174,
"name": "aaa",
"points": 1,
"is_good": true,
"image_id": "computer-game",
"user_id": "a5f05d38-7f7c-4663-b00f-3d6138e0e246"
},
"14": {
"id": "65e79bbd-6cdf-4636-9e9d-f608206dbd80",
"created_at": 1772251855.4823341,
"updated_at": 1772251855.4823341,
"name": "Be Cool \ud83d\ude0e",
"points": 5,
"type": "kindness",
"image_id": "58d4adb9-3cee-4d7c-8e90-d81173716ce5",
"user_id": "6da06108-0db8-46be-b4cb-60ce7b54564d"
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,23 +2,23 @@
"cookies": [ "cookies": [
{ {
"name": "refresh_token", "name": "refresh_token",
"value": "oQxjeMmAW3v4aoI_hBdjpMXThVDzKZTG_6bQGPJBokc", "value": "pjakYk7dm_bGf-VXaBpRXFNNq08FH0csnh3nBhCqWGw",
"domain": "localhost", "domain": "localhost",
"path": "/api/auth", "path": "/api/auth",
"expires": 1782271329.747941, "expires": 1783633314.818346,
"httpOnly": true, "httpOnly": true,
"secure": true, "secure": true,
"sameSite": "Strict" "sameSite": "Strict"
}, },
{ {
"name": "access_token", "name": "access_token",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZS1jY0B0ZXN0LmNvbSIsInVzZXJfaWQiOiIyYjQ1MmRiNi1lYTRmLTQyMDItODIzYi0zNjllN2E3YTQ5MjYiLCJ0b2tlbl92ZXJzaW9uIjowLCJleHAiOjE3NzQ0OTYyMjl9.docokOZYHgPtNNqirbfLSRABQHHsmTj-9_txuR8NHOM", "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZS1jY0B0ZXN0LmNvbSIsInVzZXJfaWQiOiI3NWQ3NGU4OC1jMjY0LTQ3MGMtOThiNC02ZTQ0YTBkYzkyOTMiLCJ0b2tlbl92ZXJzaW9uIjowLCJleHAiOjE3NzU4NTgyMTR9.lXWnj-O3MszrJ_GIwp6ViN0W5-5XEpSDVlAQIQSll-Q",
"domain": "localhost", "domain": "localhost",
"path": "/", "path": "/",
"expires": -1, "expires": 1775858214.818293,
"httpOnly": true, "httpOnly": true,
"secure": true, "secure": true,
"sameSite": "Strict" "sameSite": "Lax"
} }
], ],
"origins": [ "origins": [
@@ -27,11 +27,11 @@
"localStorage": [ "localStorage": [
{ {
"name": "authSyncEvent", "name": "authSyncEvent",
"value": "{\"type\":\"logout\",\"at\":1774495329388}" "value": "{\"type\":\"logout\",\"at\":1775857314645}"
}, },
{ {
"name": "parentAuth", "name": "parentAuth",
"value": "{\"expiresAt\":1774668130069}" "value": "{\"expiresAt\":1776030114975}"
} }
] ]
} }

View File

@@ -2,23 +2,23 @@
"cookies": [ "cookies": [
{ {
"name": "refresh_token", "name": "refresh_token",
"value": "PcQvgm3e7SXL3CHGNXYGXhRrU7erXomAQDbOzVAUrCQ", "value": "sTFHvjZ5hJxJrlNHoxdWkNJ6vxdDzrZwTRFz55JxWKs",
"domain": "localhost", "domain": "localhost",
"path": "/api/auth", "path": "/api/auth",
"expires": 1782271328.966396, "expires": 1783633314.820088,
"httpOnly": true, "httpOnly": true,
"secure": true, "secure": true,
"sameSite": "Strict" "sameSite": "Strict"
}, },
{ {
"name": "access_token", "name": "access_token",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZS1kZWxldGVAdGVzdC5jb20iLCJ1c2VyX2lkIjoiOWM4ODk0MWUtMjhiMy00YzFmLWJhNDctMDNkMTU0MTc2ODA5IiwidG9rZW5fdmVyc2lvbiI6MCwiZXhwIjoxNzc0NDk2MjI4fQ.xxzCNQEX6y07hFJzEdumbYbhm_52VWM90tHsTPDfSKs", "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZS1kZWxldGVAdGVzdC5jb20iLCJ1c2VyX2lkIjoiY2U4MmI2MzMtMDUwMy00MmMxLWFlNjMtZmEzNzcxMmMxODZiIiwidG9rZW5fdmVyc2lvbiI6MCwiZXhwIjoxNzc1ODU4MjE0fQ.meuVUYIk9NFuo66KDhLPZHoXgHRLWmRDGyR23doTLIw",
"domain": "localhost", "domain": "localhost",
"path": "/", "path": "/",
"expires": -1, "expires": 1775858214.820046,
"httpOnly": true, "httpOnly": true,
"secure": true, "secure": true,
"sameSite": "Strict" "sameSite": "Lax"
} }
], ],
"origins": [ "origins": [
@@ -27,11 +27,11 @@
"localStorage": [ "localStorage": [
{ {
"name": "authSyncEvent", "name": "authSyncEvent",
"value": "{\"type\":\"logout\",\"at\":1774495328432}" "value": "{\"type\":\"logout\",\"at\":1775857314657}"
}, },
{ {
"name": "parentAuth", "name": "parentAuth",
"value": "{\"expiresAt\":1774668129476}" "value": "{\"expiresAt\":1776030114992}"
} }
] ]
} }

View File

@@ -2,23 +2,23 @@
"cookies": [ "cookies": [
{ {
"name": "refresh_token", "name": "refresh_token",
"value": "K9dRbu3607bZL23pmoMCD8qGM697tnpfY-dYVJ7qXPU", "value": "nbOCYWuMXIGNyjN2uc6uohUFqkl-Bv1d01YjAOirGi8",
"domain": "localhost", "domain": "localhost",
"path": "/api/auth", "path": "/api/auth",
"expires": 1782271324.066253, "expires": 1783633312.982067,
"httpOnly": true, "httpOnly": true,
"secure": true, "secure": true,
"sameSite": "Strict" "sameSite": "Strict"
}, },
{ {
"name": "access_token", "name": "access_token",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZUB0ZXN0LmNvbSIsInVzZXJfaWQiOiJiMGFhNDczMC05ZDI4LTQ3NzQtODE3NC1kZTZmZWJkMjZjMmIiLCJ0b2tlbl92ZXJzaW9uIjowLCJleHAiOjE3NzQ0OTYyMjR9.3RoSBnOU30YFn61lbfKo_ZMF2LQcsKb7qO31CK4AOl8", "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZUB0ZXN0LmNvbSIsInVzZXJfaWQiOiJlNjM1MjRiOS03NmIzLTRiMmQtODc1Ni05ZmNkNTk0MDRhYTkiLCJ0b2tlbl92ZXJzaW9uIjowLCJleHAiOjE3NzU4NTgyMTJ9.ztnqGJFbFDSze0GvK-2G37v5HaDdWRK392T7vsUpnhw",
"domain": "localhost", "domain": "localhost",
"path": "/", "path": "/",
"expires": -1, "expires": 1775858212.982022,
"httpOnly": true, "httpOnly": true,
"secure": true, "secure": true,
"sameSite": "Strict" "sameSite": "Lax"
} }
], ],
"origins": [ "origins": [
@@ -27,11 +27,11 @@
"localStorage": [ "localStorage": [
{ {
"name": "authSyncEvent", "name": "authSyncEvent",
"value": "{\"type\":\"logout\",\"at\":1774495323817}" "value": "{\"type\":\"logout\",\"at\":1775857312837}"
}, },
{ {
"name": "parentAuth", "name": "parentAuth",
"value": "{\"expiresAt\":1774668124255}" "value": "{\"expiresAt\":1776030113109}"
} }
] ]
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -12,7 +12,6 @@ vi.mock('@/stores/auth', () => ({
isAuthReady: isAuthReadyMock, isAuthReady: isAuthReadyMock,
isUserLoggedIn: isUserLoggedInMock, isUserLoggedIn: isUserLoggedInMock,
isParentAuthenticated: isParentAuthenticatedMock, isParentAuthenticated: isParentAuthenticatedMock,
logoutParent: vi.fn(),
enforceParentExpiry: vi.fn(), enforceParentExpiry: vi.fn(),
})) }))

View File

@@ -28,7 +28,6 @@ import {
isUserLoggedIn, isUserLoggedIn,
isParentAuthenticated, isParentAuthenticated,
isAuthReady, isAuthReady,
logoutParent,
enforceParentExpiry, enforceParentExpiry,
} from '../stores/auth' } from '../stores/auth'
import ParentPinSetup from '@/components/auth/ParentPinSetup.vue' import ParentPinSetup from '@/components/auth/ParentPinSetup.vue'
@@ -304,8 +303,6 @@ router.beforeEach(async (to, from, next) => {
if (isParentAuthenticated.value) { if (isParentAuthenticated.value) {
return next('/parent') return next('/parent')
} else { } else {
// Ensure parent auth is fully cleared when redirecting away from /parent
logoutParent()
return next('/child') return next('/child')
} }
}) })

View File

@@ -12,6 +12,31 @@ export const isParentAuthenticated = ref(false)
export const isParentPersistent = ref(false) export const isParentPersistent = ref(false)
export const parentAuthExpiresAt = ref<number | null>(null) export const parentAuthExpiresAt = ref<number | null>(null)
// --- Background expiry watcher ---
// Declared before the init block so startParentExpiryWatcher is safe to call during restore.
let expiryWatcherIntervalId: ReturnType<typeof setInterval> | null = null
function runExpiryCheck() {
if (parentAuthExpiresAt.value !== null && Date.now() >= parentAuthExpiresAt.value) {
applyParentLoggedOutState()
if (typeof window !== 'undefined') {
window.location.href = '/child'
}
}
}
export function startParentExpiryWatcher() {
stopParentExpiryWatcher()
expiryWatcherIntervalId = setInterval(runExpiryCheck, 15_000)
}
export function stopParentExpiryWatcher() {
if (expiryWatcherIntervalId !== null) {
clearInterval(expiryWatcherIntervalId)
expiryWatcherIntervalId = null
}
}
// Restore persistent parent auth from localStorage on store init // Restore persistent parent auth from localStorage on store init
if (hasLocalStorage) { if (hasLocalStorage) {
try { try {
@@ -38,30 +63,6 @@ export const currentUserId = ref('')
export const suppressForceLogout = ref(false) export const suppressForceLogout = ref(false)
let authSyncInitialized = false let authSyncInitialized = false
// --- Background expiry watcher ---
let expiryWatcherIntervalId: ReturnType<typeof setInterval> | null = null
function runExpiryCheck() {
if (parentAuthExpiresAt.value !== null && Date.now() >= parentAuthExpiresAt.value) {
applyParentLoggedOutState()
if (typeof window !== 'undefined') {
window.location.href = '/child'
}
}
}
export function startParentExpiryWatcher() {
stopParentExpiryWatcher()
expiryWatcherIntervalId = setInterval(runExpiryCheck, 15_000)
}
export function stopParentExpiryWatcher() {
if (expiryWatcherIntervalId !== null) {
clearInterval(expiryWatcherIntervalId)
expiryWatcherIntervalId = null
}
}
/** /**
* Explicitly checks whether parent auth has expired and clears it if so. * Explicitly checks whether parent auth has expired and clears it if so.
* Called by the router guard before allowing /parent routes. * Called by the router guard before allowing /parent routes.
@@ -163,10 +164,12 @@ export async function checkAuth() {
currentUserId.value = data.id currentUserId.value = data.id
isUserLoggedIn.value = true isUserLoggedIn.value = true
} else { } else {
logoutUser() isUserLoggedIn.value = false
currentUserId.value = ''
} }
} catch { } catch {
logoutUser() isUserLoggedIn.value = false
currentUserId.value = ''
} }
isAuthReady.value = true isAuthReady.value = true
} }