Skip to content

Commit c56bee3

Browse files
author
HugoFara
committed
refactor(routes): use RESTful /id/edit URLs instead of ?chg= query params
- /languages?chg=123 → /languages/123/edit - /texts?chg=123 → /texts/123/edit - /words/edit?chg=123 → /words/123/edit Updated controllers, views, frontend TypeScript, and E2E tests.
1 parent 2b3b4cf commit c56bee3

File tree

15 files changed

+141
-66
lines changed

15 files changed

+141
-66
lines changed

cypress/e2e/04-languages.cy.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ describe('Languages Management', () => {
5353
it('should have edit links for languages when they exist', () => {
5454
cy.get('body').then(($body) => {
5555
if ($body.find('.language-card').length > 0) {
56-
cy.get('.language-card a[href*="chg="]').should('exist');
56+
cy.get('.language-card a[href*="/edit"]').should('exist');
5757
} else {
5858
cy.log('No languages installed - skipping edit links check');
5959
}
@@ -71,7 +71,7 @@ describe('Languages Management', () => {
7171
});
7272

7373
it('should have "New Language" button in action card', () => {
74-
cy.get('.action-card a[href*="new=1"]').should('exist');
74+
cy.get('.action-card a[href*="/languages/new"]').should('exist');
7575
});
7676

7777
it('should have "Quick Setup Wizard" button', () => {
@@ -100,9 +100,9 @@ describe('Languages Management', () => {
100100

101101
it('should navigate to edit page when Edit is clicked', () => {
102102
cy.get('body').then(($body) => {
103-
if ($body.find('.language-card a[href*="chg="]').length > 0) {
104-
cy.get('.language-card a[href*="chg="]').first().click();
105-
cy.url().should('include', 'chg=');
103+
if ($body.find('.language-card a[href*="/edit"]').length > 0) {
104+
cy.get('.language-card a[href*="/edit"]').first().click();
105+
cy.url().should('match', /\/languages\/\d+\/edit/);
106106
cy.get('form').should('exist');
107107
} else {
108108
cy.log('No languages installed - skipping edit navigation check');
@@ -233,19 +233,19 @@ describe('Languages Management', () => {
233233

234234
describe('Edit Language', () => {
235235
it('should load edit form for existing language', () => {
236-
cy.visit('/languages?chg=1');
236+
cy.visit('/languages/1/edit');
237237
cy.get('form[name="lg_form"]').should('exist');
238238
// Language name should be filled
239239
cy.get('input[name="LgName"]').should('not.have.value', '');
240240
});
241241

242242
it('should have populated fields', () => {
243-
cy.visit('/languages?chg=1');
243+
cy.visit('/languages/1/edit');
244244
cy.get('input[name="LgName"]').invoke('val').should('not.be.empty');
245245
});
246246

247247
it('should have cancel button that returns to list', () => {
248-
cy.visit('/languages?chg=1');
248+
cy.visit('/languages/1/edit');
249249
cy.get('button').contains('Cancel').click();
250250
cy.url().should('eq', Cypress.config().baseUrl + '/languages');
251251
});

cypress/e2e/05-texts.cy.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ describe('Texts Management', () => {
3535
it('should have action links for texts', () => {
3636
// Look for read/test/edit links in cards or action card for new text
3737
cy.get(
38-
'a[href*="/text/read"], a[href*="/test"], a[href*="chg="], a[href*="new=1"]'
38+
'a[href*="/text/read"], a[href*="/test"], a[href*="/edit"], a[href*="/texts/new"]'
3939
).should('exist');
4040
});
4141
});
@@ -130,7 +130,7 @@ describe('Texts Management', () => {
130130

131131
describe('Edit Text', () => {
132132
it('should load edit form for existing text or show not found', () => {
133-
cy.visit('/text/edit?chg=1');
133+
cy.visit('/texts/1/edit');
134134
// Either show the form or a "not found" message
135135
cy.get('body').then(($body) => {
136136
if ($body.text().includes('not found')) {
@@ -144,7 +144,7 @@ describe('Texts Management', () => {
144144
});
145145

146146
it('should have populated title field when text exists', () => {
147-
cy.visit('/text/edit?chg=1');
147+
cy.visit('/texts/1/edit');
148148
cy.get('body').then(($body) => {
149149
if (!$body.text().includes('not found')) {
150150
cy.get('input[name="TxTitle"]').invoke('val').should('not.be.empty');

cypress/e2e/06-words.cy.ts

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -75,23 +75,23 @@ describe('Words Management', () => {
7575
});
7676

7777
describe('Single Word Edit', () => {
78-
// Note: Word editing from the list uses /words/edit?chg=X
78+
// Note: Word editing from the list uses /words/X/edit RESTful route
7979
// The /word/edit endpoint is for editing from within reading context
8080
// These tests verify editing works when a word exists in the database
8181

8282
it('should load word edit page when word exists', () => {
8383
cy.visit('/words/edit');
8484
cy.get('body').then(($body) => {
85-
if (!$body.text().includes('No terms found') && $body.find('a[href*="chg="]').length > 0) {
86-
cy.get('a[href*="chg="]')
85+
if (!$body.text().includes('No terms found') && $body.find('a[href*="/edit"]').length > 0) {
86+
cy.get('a[href*="/edit"]')
8787
.first()
8888
.then(($link) => {
8989
const href = $link.attr('href');
9090
if (href) {
91-
const match = href.match(/chg=(\d+)/);
91+
const match = href.match(/\/words\/(\d+)\/edit/);
9292
if (match) {
9393
const wordId = match[1];
94-
cy.visit(`/words/edit?chg=${wordId}`);
94+
cy.visit(`/words/${wordId}/edit`);
9595
cy.get('form').should('exist');
9696
}
9797
}
@@ -105,15 +105,15 @@ describe('Words Management', () => {
105105
it('should have word text field when editing', () => {
106106
cy.visit('/words/edit');
107107
cy.get('body').then(($body) => {
108-
if (!$body.text().includes('No terms found') && $body.find('a[href*="chg="]').length > 0) {
109-
cy.get('a[href*="chg="]')
108+
if (!$body.text().includes('No terms found') && $body.find('a[href*="/edit"]').length > 0) {
109+
cy.get('a[href*="/edit"]')
110110
.first()
111111
.then(($link) => {
112112
const href = $link.attr('href');
113113
if (href) {
114-
const match = href.match(/chg=(\d+)/);
114+
const match = href.match(/\/words\/(\d+)\/edit/);
115115
if (match) {
116-
cy.visit(`/words/edit?chg=${match[1]}`);
116+
cy.visit(`/words/${match[1]}/edit`);
117117
cy.get('input[name="WoText"]').should('exist');
118118
}
119119
}
@@ -127,15 +127,15 @@ describe('Words Management', () => {
127127
it('should have translation field when editing', () => {
128128
cy.visit('/words/edit');
129129
cy.get('body').then(($body) => {
130-
if (!$body.text().includes('No terms found') && $body.find('a[href*="chg="]').length > 0) {
131-
cy.get('a[href*="chg="]')
130+
if (!$body.text().includes('No terms found') && $body.find('a[href*="/edit"]').length > 0) {
131+
cy.get('a[href*="/edit"]')
132132
.first()
133133
.then(($link) => {
134134
const href = $link.attr('href');
135135
if (href) {
136-
const match = href.match(/chg=(\d+)/);
136+
const match = href.match(/\/words\/(\d+)\/edit/);
137137
if (match) {
138-
cy.visit(`/words/edit?chg=${match[1]}`);
138+
cy.visit(`/words/${match[1]}/edit`);
139139
cy.get('textarea[name="WoTranslation"]').should('exist');
140140
}
141141
}
@@ -149,15 +149,15 @@ describe('Words Management', () => {
149149
it('should have status selector when editing', () => {
150150
cy.visit('/words/edit');
151151
cy.get('body').then(($body) => {
152-
if (!$body.text().includes('No terms found') && $body.find('a[href*="chg="]').length > 0) {
153-
cy.get('a[href*="chg="]')
152+
if (!$body.text().includes('No terms found') && $body.find('a[href*="/edit"]').length > 0) {
153+
cy.get('a[href*="/edit"]')
154154
.first()
155155
.then(($link) => {
156156
const href = $link.attr('href');
157157
if (href) {
158-
const match = href.match(/chg=(\d+)/);
158+
const match = href.match(/\/words\/(\d+)\/edit/);
159159
if (match) {
160-
cy.visit(`/words/edit?chg=${match[1]}`);
160+
cy.visit(`/words/${match[1]}/edit`);
161161
cy.get('input[type="radio"][name="WoStatus"]').should('exist');
162162
}
163163
}
@@ -171,15 +171,15 @@ describe('Words Management', () => {
171171
it('should have submit button when editing', () => {
172172
cy.visit('/words/edit');
173173
cy.get('body').then(($body) => {
174-
if (!$body.text().includes('No terms found') && $body.find('a[href*="chg="]').length > 0) {
175-
cy.get('a[href*="chg="]')
174+
if (!$body.text().includes('No terms found') && $body.find('a[href*="/edit"]').length > 0) {
175+
cy.get('a[href*="/edit"]')
176176
.first()
177177
.then(($link) => {
178178
const href = $link.attr('href');
179179
if (href) {
180-
const match = href.match(/chg=(\d+)/);
180+
const match = href.match(/\/words\/(\d+)\/edit/);
181181
if (match) {
182-
cy.visit(`/words/edit?chg=${match[1]}`);
182+
cy.visit(`/words/${match[1]}/edit`);
183183
cy.get('input[type="submit"][value="Change"]').should('exist');
184184
}
185185
}
@@ -195,15 +195,15 @@ describe('Words Management', () => {
195195
it('should be able to change word status when words exist', () => {
196196
cy.visit('/words/edit');
197197
cy.get('body').then(($body) => {
198-
if (!$body.text().includes('No terms found') && $body.find('a[href*="chg="]').length > 0) {
199-
cy.get('a[href*="chg="]')
198+
if (!$body.text().includes('No terms found') && $body.find('a[href*="/edit"]').length > 0) {
199+
cy.get('a[href*="/edit"]')
200200
.first()
201201
.then(($link) => {
202202
const href = $link.attr('href');
203203
if (href) {
204-
const match = href.match(/chg=(\d+)/);
204+
const match = href.match(/\/words\/(\d+)\/edit/);
205205
if (match) {
206-
cy.visit(`/words/edit?chg=${match[1]}`);
206+
cy.visit(`/words/${match[1]}/edit`);
207207
cy.get('input[type="radio"][name="WoStatus"]').should(
208208
'have.length.greaterThan',
209209
0
@@ -222,15 +222,15 @@ describe('Words Management', () => {
222222
it('should have lemma field when editing a word', () => {
223223
cy.visit('/words/edit');
224224
cy.get('body').then(($body) => {
225-
if (!$body.text().includes('No terms found') && $body.find('a[href*="chg="]').length > 0) {
226-
cy.get('a[href*="chg="]')
225+
if (!$body.text().includes('No terms found') && $body.find('a[href*="/edit"]').length > 0) {
226+
cy.get('a[href*="/edit"]')
227227
.first()
228228
.then(($link) => {
229229
const href = $link.attr('href');
230230
if (href) {
231-
const match = href.match(/chg=(\d+)/);
231+
const match = href.match(/\/words\/(\d+)\/edit/);
232232
if (match) {
233-
cy.visit(`/words/edit?chg=${match[1]}`);
233+
cy.visit(`/words/${match[1]}/edit`);
234234
cy.get('input[name="WoLemma"]').should('exist');
235235
}
236236
}
@@ -244,15 +244,15 @@ describe('Words Management', () => {
244244
it('should have placeholder text for lemma field', () => {
245245
cy.visit('/words/edit');
246246
cy.get('body').then(($body) => {
247-
if (!$body.text().includes('No terms found') && $body.find('a[href*="chg="]').length > 0) {
248-
cy.get('a[href*="chg="]')
247+
if (!$body.text().includes('No terms found') && $body.find('a[href*="/edit"]').length > 0) {
248+
cy.get('a[href*="/edit"]')
249249
.first()
250250
.then(($link) => {
251251
const href = $link.attr('href');
252252
if (href) {
253-
const match = href.match(/chg=(\d+)/);
253+
const match = href.match(/\/words\/(\d+)\/edit/);
254254
if (match) {
255-
cy.visit(`/words/edit?chg=${match[1]}`);
255+
cy.visit(`/words/${match[1]}/edit`);
256256
cy.get('input[name="WoLemma"]')
257257
.should('have.attr', 'placeholder')
258258
.and('include', 'Base form');
@@ -268,15 +268,15 @@ describe('Words Management', () => {
268268
it('should allow entering a lemma value', () => {
269269
cy.visit('/words/edit');
270270
cy.get('body').then(($body) => {
271-
if (!$body.text().includes('No terms found') && $body.find('a[href*="chg="]').length > 0) {
272-
cy.get('a[href*="chg="]')
271+
if (!$body.text().includes('No terms found') && $body.find('a[href*="/edit"]').length > 0) {
272+
cy.get('a[href*="/edit"]')
273273
.first()
274274
.then(($link) => {
275275
const href = $link.attr('href');
276276
if (href) {
277-
const match = href.match(/chg=(\d+)/);
277+
const match = href.match(/\/words\/(\d+)\/edit/);
278278
if (match) {
279-
cy.visit(`/words/edit?chg=${match[1]}`);
279+
cy.visit(`/words/${match[1]}/edit`);
280280
cy.get('input[name="WoLemma"]')
281281
.clear()
282282
.type('testlemma')

src/Modules/Book/Views/show.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@
123123
<a href="/text/<?php echo $chapter['id']; ?>/read" class="button is-small is-primary">
124124
<?php echo IconHelper::render('book-open', ['alt' => 'Read']); ?>
125125
</a>
126-
<a href="/texts?chg=<?php echo $chapter['id']; ?>" class="button is-small is-light">
126+
<a href="/texts/<?php echo $chapter['id']; ?>/edit" class="button is-small is-light">
127127
<?php echo IconHelper::render('edit', ['alt' => 'Edit']); ?>
128128
</a>
129129
</td>

src/Modules/Language/Views/index.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ class="card-footer-item"
218218
<span>Reparse</span>
219219
</a>
220220
</template>
221-
<a :href="'/languages?chg=' + lang.id" class="card-footer-item">
221+
<a :href="'/languages/' + lang.id + '/edit'" class="card-footer-item">
222222
<span class="icon">
223223
<i data-lucide="file-pen" style="width: 16px; height: 16px;"></i>
224224
</span>

src/Modules/Text/Http/TextController.php

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,61 @@ public function new(array $params): ?RedirectResponse
247247
return null;
248248
}
249249

250+
/**
251+
* Edit single text form.
252+
*
253+
* Route: GET/POST /texts/{id}/edit
254+
*
255+
* @param int $id Text ID from route parameter
256+
*
257+
* @return RedirectResponse|null Redirect response or null if rendered
258+
*
259+
* @psalm-suppress UnusedVariable Variables are used in included view files
260+
*/
261+
public function editSingle(int $id): ?RedirectResponse
262+
{
263+
include_once self::BACKEND_PATH . '/Core/Bootstrap/db_bootstrap.php';
264+
include_once dirname(__DIR__) . '/Application/Services/TextStatisticsService.php';
265+
include_once dirname(__DIR__, 2) . '/Text/Application/Services/SentenceService.php';
266+
include_once dirname(__DIR__) . '/Application/Services/AnnotationService.php';
267+
include_once dirname(__DIR__) . '/Application/Services/TextNavigationService.php';
268+
include_once dirname(__DIR__, 2) . '/Vocabulary/Application/UseCases/FindSimilarTerms.php';
269+
include_once dirname(__DIR__, 2) . '/Vocabulary/Application/Services/ExpressionService.php';
270+
include_once __DIR__ . '/../../../Shared/Infrastructure/Database/Restore.php';
271+
include_once dirname(__DIR__, 2) . '/Admin/Application/Services/MediaService.php';
272+
include_once self::BACKEND_PATH . '/Core/Bootstrap/start_session.php';
273+
include_once self::BACKEND_PATH . '/Core/Integration/YouTubeImport.php';
274+
275+
// Get filter parameters
276+
$currentLang = Validation::language(
277+
InputValidator::getStringWithDb("filterlang", 'currentlanguage')
278+
);
279+
280+
// Handle save operation
281+
$op = $this->param('op');
282+
if ($op !== '') {
283+
$noPagestart = (substr($op, -8) == 'and Open');
284+
if (!$noPagestart) {
285+
PageLayoutHelper::renderPageStart('Texts', true);
286+
}
287+
$result = $this->handleTextOperation($op, $noPagestart, $currentLang);
288+
if ($result instanceof RedirectResponse) {
289+
return $result;
290+
}
291+
if ($result['redirect']) {
292+
return null;
293+
}
294+
PageLayoutHelper::renderPageEnd();
295+
return null;
296+
}
297+
298+
PageLayoutHelper::renderPageStart('Texts', true);
299+
$this->showEditTextForm($id);
300+
PageLayoutHelper::renderPageEnd();
301+
302+
return null;
303+
}
304+
250305
/**
251306
* Edit texts list (replaces text_edit.php)
252307
*
@@ -325,12 +380,8 @@ public function edit(array $params): ?RedirectResponse
325380
}
326381
}
327382

328-
// Display appropriate page
329-
if ($this->hasParam('chg')) {
330-
$this->showEditTextForm($this->paramInt('chg', 0) ?? 0);
331-
} else {
332-
$this->showTextsList($currentLang, $message);
333-
}
383+
// Display texts list
384+
$this->showTextsList($currentLang, $message);
334385

335386
PageLayoutHelper::renderPageEnd();
336387

src/Modules/Text/Views/edit_list.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ class="dropdown-menu card-dropdown"
312312
<?php echo IconHelper::render('archive', ['size' => 14]); ?>
313313
<span>Archive</span>
314314
</a>
315-
<a :href="'/texts?chg=' + text.id" class="dropdown-item">
315+
<a :href="'/texts/' + text.id + '/edit'" class="dropdown-item">
316316
<?php echo IconHelper::render('file-pen', ['size' => 14]); ?>
317317
<span>Edit</span>
318318
</a>

src/Modules/Text/Views/print_alpine.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@
102102
<?php if ($mode !== 'edit') : ?>
103103
<?php echo (new AnnotationService())->getAnnotationLink($textId); ?>
104104
<?php endif; ?>
105-
<a target="_top" href="/texts?chg=<?php echo $textId; ?>">
105+
<a target="_top" href="/texts/<?php echo $textId; ?>/edit">
106106
<?php echo IconHelper::render('file-pen', ['title' => 'Edit Text', 'alt' => 'Edit Text']); ?>
107107
</a>
108108
</div>

src/Modules/Text/Views/read_desktop.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@
105105
<a href="/text/print-plain?text=<?php echo $textId; ?>" class="button is-small">Print</a>
106106
</div>
107107
<div class="control">
108-
<a href="/texts?chg=<?php echo $textId; ?>" class="button is-small">Edit</a>
108+
<a href="/texts/<?php echo $textId; ?>/edit" class="button is-small">Edit</a>
109109
</div>
110110
<div class="control">
111111
<button class="button is-small" :class="showAll ? 'is-info' : 'is-light'" @click="toggleShowAll">

0 commit comments

Comments
 (0)