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') + }) + }) +}) 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({ 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)); +} 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/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(); 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/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 @@
+ 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 @@ + diff --git a/views/templates/today.php b/views/templates/today.php index 4a5ed8a..e6c880f 100644 --- a/views/templates/today.php +++ b/views/templates/today.php @@ -12,6 +12,9 @@

Today

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

+ diff --git a/views/templates/userText.php b/views/templates/userText.php index c9b568a..51e5adb 100644 --- a/views/templates/userText.php +++ b/views/templates/userText.php @@ -9,14 +9,22 @@
+ + 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 @@ +