From 2a750625146a51ce5608b00ae71fc861167139ae Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Sun, 3 May 2026 17:11:12 +0300 Subject: [PATCH 1/8] add logout tests for all authed pages --- cypress/e2e/auth.cy.js | 54 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/cypress/e2e/auth.cy.js b/cypress/e2e/auth.cy.js index ae22d22..bc44396 100644 --- a/cypress/e2e/auth.cy.js +++ b/cypress/e2e/auth.cy.js @@ -69,6 +69,60 @@ describe('Authentication flows', () => { cy.url().should('include', '/login') }) + it('logout button on today page works', () => { + cy.loginAsUser() + cy.visit('/today') + cy.get('#logout').click() + cy.url().should('include', '/login') + cy.visit('/today') + cy.url().should('include', '/login') + }) + + it('logout button on user texts list page works', () => { + cy.loginAsUser() + cy.visit('/texts') + cy.get('#logout').click() + cy.url().should('include', '/login') + cy.visit('/texts') + cy.url().should('include', '/login') + }) + + it('logout button on user specific text page works', () => { + cy.loginAsUser() + cy.visit('/texts/0') + cy.get('#logout').click() + cy.url().should('include', '/login') + cy.visit('/texts/0') + cy.url().should('include', '/login') + }) + + it('logout button on admin page works', () => { + cy.loginAsAdmin() + cy.visit('/admin') + cy.get('#logout').click() + cy.url().should('include', '/login') + cy.visit('/admin') + cy.url().should('include', '/login') + }) + + it('logout button on admin texts list page works', () => { + cy.loginAsAdmin() + cy.visit('/admin/texts') + cy.get('#logout').click() + cy.url().should('include', '/login') + cy.visit('/admin/texts') + cy.url().should('include', '/login') + }) + + it('logout button on admin specific text page works', () => { + cy.loginAsAdmin() + cy.visit('/admin/texts/0') + cy.get('#logout').click() + cy.url().should('include', '/login') + cy.visit('/admin/texts/0') + cy.url().should('include', '/login') + }) + it('non-admin user hitting /admin gets 403', () => { cy.loginAsUser() cy.request({ From e83f098280980b7831c348d9501a3edde48f9e48 Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Sun, 3 May 2026 17:25:08 +0300 Subject: [PATCH 2/8] add seeding for manual testing --- data/seedMore.php | 127 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 data/seedMore.php diff --git a/data/seedMore.php b/data/seedMore.php new file mode 100644 index 0000000..abea97f --- /dev/null +++ b/data/seedMore.php @@ -0,0 +1,127 @@ + 0, + 'name' => 'Tanach', + 'userId' => 1, + ], +]; + +$nodes = [ + [ + 'id' => 0, + 'title' => 'Tanach', + 'textId' => 0, + 'parentNodeId' => null, + ], + [ + 'id' => 1, + 'title' => 'Torah', + 'textId' => 0, + 'parentNodeId' => 0, + ], + [ + 'id' => 2, + 'title' => 'Neviim', + 'textId' => 0, + 'parentNodeId' => 0, + ], + [ + 'id' => 3, + 'title' => 'Kesuvim', + 'textId' => 0, + 'parentNodeId' => 0, + ], + [ + 'id' => 4, + 'title' => 'Bereishis', + 'textId' => 0, + 'parentNodeId' => 1, + ], + [ + 'id' => 5, + 'title' => 'Shmos', + 'textId' => 0, + 'parentNodeId' => 1, + ], + [ + 'id' => 6, + 'title' => 'Vayikra', + 'textId' => 0, + 'parentNodeId' => 1, + ], + [ + 'id' => 7, + 'title' => 'Bamidbar', + 'textId' => 0, + 'parentNodeId' => 1, + ], + [ + 'id' => 8, + 'title' => 'Devarim', + 'textId' => 0, + 'parentNodeId' => 1, + ], + [ + 'id' => 9, + 'title' => 'Bereishis', + 'textId' => 0, + 'parentNodeId' => 4, + ], + [ + 'id' => 10, + 'title' => 'Noach', + 'textId' => 0, + 'parentNodeId' => 4, + ], + [ + 'id' => 11, + 'title' => 'Lech Lecha', + 'textId' => 0, + 'parentNodeId' => 4, + ], +]; + +// Default credentials: +// admin@example.com / admin1234 (admin) +// user@example.com / password1 (regular user) +// user2@example.com / password2 (second regular user, no texts seeded) +$users = [ + [ + 'id' => 0, + 'email' => 'admin@example.com', + 'passwordHash' => password_hash('admin1234', PASSWORD_DEFAULT), + 'isAdmin' => true, + ], + [ + 'id' => 1, + 'email' => 'user@example.com', + 'passwordHash' => password_hash('password1', PASSWORD_DEFAULT), + 'isAdmin' => false, + ], + [ + 'id' => 2, + 'email' => 'user2@example.com', + 'passwordHash' => password_hash('password2', PASSWORD_DEFAULT), + 'isAdmin' => false, + ], +]; + +$plans = []; +$scheduledNodes = []; +$sessions = []; + +$fileDataMap = [ + 'texts.json' => $texts, + 'nodes.json' => $nodes, + 'users.json' => $users, + 'plans.json' => $plans, + 'scheduledNodes.json' => $scheduledNodes, + 'sessions.json' => $sessions, +]; + +foreach ($fileDataMap as $file => $data) { + $path = __DIR__ . "/$file"; + file_put_contents($path, json_encode($data, JSON_PRETTY_PRINT)); +} From 33668e29303b0dc4f11d89b8299c7d87419f32d6 Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Sun, 3 May 2026 17:25:31 +0300 Subject: [PATCH 3/8] add logout button to today and userText --- views/templates/today.php | 1 + views/templates/userText.php | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/views/templates/today.php b/views/templates/today.php index 4a5ed8a..46e85b4 100644 --- a/views/templates/today.php +++ b/views/templates/today.php @@ -12,6 +12,7 @@

Today

Home +
diff --git a/views/templates/userText.php b/views/templates/userText.php index c9b568a..5b2626d 100644 --- a/views/templates/userText.php +++ b/views/templates/userText.php @@ -9,14 +9,19 @@
+ From 17ab181adb4d37e8a684cd930a7d36f90d1d8736 Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Sun, 3 May 2026 17:32:48 +0300 Subject: [PATCH 4/8] guard text.js node fetch on non-ok response --- public/js/text.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/public/js/text.js b/public/js/text.js index 2b021d0..7566869 100644 --- a/public/js/text.js +++ b/public/js/text.js @@ -32,8 +32,15 @@ document.addEventListener('DOMContentLoaded', () => { function fetchAndRenderNodes(textId) { return fetch('/api/nodes/' + textId, { credentials: 'same-origin' }) - .then(res => res.json()) + .then(function (response) { + if (!response.ok) { + return null; + } + return response.json(); + }) .then(nodes => { + if (!Array.isArray(nodes)) return; + const existing = document.querySelector('#text-detail > ul'); if (existing) existing.remove(); From 9c062c4f0e056db30fdb4c3b602f79e011a83ac1 Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Sun, 3 May 2026 17:33:01 +0300 Subject: [PATCH 5/8] add logout button to admin texts page --- views/templates/texts.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/views/templates/texts.php b/views/templates/texts.php index a37bd1e..2da7733 100644 --- a/views/templates/texts.php +++ b/views/templates/texts.php @@ -14,6 +14,7 @@ Back to Admin + @@ -31,6 +32,7 @@ + From e2b1371452c846c3c6bd8bc83ca743d1d67873be Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Sun, 3 May 2026 17:33:11 +0300 Subject: [PATCH 6/8] add logout button to admin text page --- views/templates/text.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/views/templates/text.php b/views/templates/text.php index 6d41421..6c62559 100644 --- a/views/templates/text.php +++ b/views/templates/text.php @@ -9,14 +9,18 @@
+ From a62b8763f0fcd50188a7f4a0e91b92e29c1db4d0 Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Sun, 3 May 2026 19:13:50 +0300 Subject: [PATCH 7/8] test admin nav link visibility --- cypress/e2e/adminNavLink.cy.js | 70 ++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 cypress/e2e/adminNavLink.cy.js diff --git a/cypress/e2e/adminNavLink.cy.js b/cypress/e2e/adminNavLink.cy.js new file mode 100644 index 0000000..0b0ae36 --- /dev/null +++ b/cypress/e2e/adminNavLink.cy.js @@ -0,0 +1,70 @@ +describe('The admin nav link', () => { + beforeEach(() => { + cy.exec('npm run db:seed') + }) + afterEach(() => { + cy.exec('npm run db:wipe') + }) + + describe('when logged in as an admin', () => { + beforeEach(() => { + cy.loginAsAdmin() + }) + + it('shows the admin link on the home page', () => { + cy.visit('/home') + cy.get('#admin-link') + .should('be.visible') + .and('have.attr', 'href', '/admin') + }) + + it('shows the admin link on the today page', () => { + cy.visit('/today') + cy.get('#admin-link') + .should('be.visible') + .and('have.attr', 'href', '/admin') + }) + + it('shows the admin link on the user texts page', () => { + cy.visit('/texts') + cy.get('#admin-link') + .should('be.visible') + .and('have.attr', 'href', '/admin') + }) + + it('navigates to the admin page when clicked', () => { + cy.visit('/home') + cy.get('#admin-link').click() + cy.url().should('include', '/admin') + cy.get('h1').should('contain', 'Admin') + }) + }) + + describe('when logged in as a regular user', () => { + beforeEach(() => { + cy.loginAsUser() + }) + + it('does not show the admin link on the home page', () => { + cy.visit('/home') + cy.get('#admin-link').should('not.be.visible') + }) + + it('does not show the admin link on the today page', () => { + cy.visit('/today') + cy.get('#admin-link').should('not.be.visible') + }) + + it('does not show the admin link on the user texts page', () => { + cy.visit('/texts') + cy.get('#admin-link').should('not.be.visible') + }) + + it('does not show the admin link on a user text page', () => { + cy.intercept('GET', '/api/texts/0').as('getText') + cy.visit('/texts/0') + cy.wait('@getText') + cy.get('#admin-link').should('not.be.visible') + }) + }) +}) From a6f7abe2fd97933158f5facf17c784854453bd64 Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Sun, 3 May 2026 19:18:44 +0300 Subject: [PATCH 8/8] add admin nav link --- public/js/nav.js | 18 ++++++++++++++++++ views/templates/home.php | 3 +++ views/templates/today.php | 3 +++ views/templates/userText.php | 3 +++ views/templates/userTexts.php | 3 +++ 5 files changed, 30 insertions(+) create mode 100644 public/js/nav.js diff --git a/public/js/nav.js b/public/js/nav.js new file mode 100644 index 0000000..f0ac386 --- /dev/null +++ b/public/js/nav.js @@ -0,0 +1,18 @@ +document.addEventListener('DOMContentLoaded', async () => { + const adminLink = document.getElementById('admin-link'); + if (adminLink === null) { + return; + } + + const response = await fetch('/api/auth/me', { + credentials: 'same-origin', + }); + if (!response.ok) { + return; + } + + const body = await response.json(); + if (body.user && body.user.isAdmin === true) { + adminLink.hidden = false; + } +}); diff --git a/views/templates/home.php b/views/templates/home.php index 63cfbfd..2a0e13c 100644 --- a/views/templates/home.php +++ b/views/templates/home.php @@ -17,6 +17,8 @@ Today's schedule + @@ -43,6 +45,7 @@ + diff --git a/views/templates/today.php b/views/templates/today.php index 46e85b4..e6c880f 100644 --- a/views/templates/today.php +++ b/views/templates/today.php @@ -12,6 +12,8 @@

Today

Home +
@@ -23,6 +25,7 @@

+ diff --git a/views/templates/userText.php b/views/templates/userText.php index 5b2626d..51e5adb 100644 --- a/views/templates/userText.php +++ b/views/templates/userText.php @@ -14,6 +14,8 @@ My texts + @@ -22,6 +24,7 @@
+ diff --git a/views/templates/userTexts.php b/views/templates/userTexts.php index a90ca94..dbb6f5b 100644 --- a/views/templates/userTexts.php +++ b/views/templates/userTexts.php @@ -14,6 +14,8 @@ Back to Home + @@ -33,6 +35,7 @@ +