refactor: split links controller into multiple controllers

This commit is contained in:
Sonny
2025-08-17 00:39:51 +02:00
parent 56b52adac0
commit 9ff3ca112c
16 changed files with 260 additions and 208 deletions

View File

@@ -0,0 +1,30 @@
import { CollectionService } from '#collections/services/collection_service';
import { LinkService } from '#links/services/link_service';
import { createLinkValidator } from '#links/validators/create_link_validator';
import { inject } from '@adonisjs/core';
import { HttpContext } from '@adonisjs/core/http';
@inject()
export default class CreateLinkController {
constructor(
private linkService: LinkService,
private collectionsService: CollectionService
) {}
async render({ inertia }: HttpContext) {
const collections =
await this.collectionsService.getCollectionsForAuthenticatedUser();
return inertia.render('links/create', { collections });
}
async execute({ request }: HttpContext) {
const { collectionId, ...payload } =
await request.validateUsing(createLinkValidator);
await this.linkService.createLink({
...payload,
collectionId,
});
return this.collectionsService.redirectToCollectionId(collectionId);
}
}

View File

@@ -0,0 +1,39 @@
import { CollectionService } from '#collections/services/collection_service';
import { LinkService } from '#links/services/link_service';
import { deleteLinkValidator } from '#links/validators/delete_link_validator';
import { inject } from '@adonisjs/core';
import { HttpContext } from '@adonisjs/core/http';
import db from '@adonisjs/lucid/services/db';
@inject()
export default class DeleteLinkController {
constructor(
protected collectionsService: CollectionService,
protected linkService: LinkService
) {}
async render({ auth, inertia, request }: HttpContext) {
const linkId = request.qs()?.linkId;
if (!linkId) {
return this.collectionsService.redirectToDashboard();
}
const link = await this.linkService.getLinkById(linkId, auth.user!.id);
await link.load('collection');
return inertia.render('links/delete', { link });
}
async execute({ request, auth }: HttpContext) {
const { params } = await request.validateUsing(deleteLinkValidator);
const link = await this.linkService.getLinkById(params.id, auth.user!.id);
await this.linkService.deleteLink(params.id);
return this.collectionsService.redirectToCollectionId(link.collectionId);
}
async getTotalLinksCount() {
const totalCount = await db.from('links').count('* as total');
return Number(totalCount[0].total);
}
}

View File

@@ -1,129 +0,0 @@
import CollectionsController from '#collections/controllers/show_collections_controller';
import Link from '#links/models/link';
import { createLinkValidator } from '#links/validators/create_link_validator';
import { deleteLinkValidator } from '#links/validators/delete_link_validator';
import { updateLinkFavoriteStatusValidator } from '#links/validators/update_favorite_link_validator';
import { updateLinkValidator } from '#links/validators/update_link_validator';
import { inject } from '@adonisjs/core';
import { HttpContext } from '@adonisjs/core/http';
import db from '@adonisjs/lucid/services/db';
@inject()
export default class LinksController {
constructor(protected collectionsController: CollectionsController) {}
async showCreatePage({ auth, inertia }: HttpContext) {
const collections =
await this.collectionsController.getCollectionsByAuthorId(auth.user!.id);
return inertia.render('links/create', { collections });
}
async store({ auth, request, response }: HttpContext) {
const { collectionId, ...payload } =
await request.validateUsing(createLinkValidator);
await this.collectionsController.getCollectionById(
collectionId,
auth.user!.id
);
await Link.create({
...payload,
collectionId,
authorId: auth.user?.id!,
});
return this.collectionsController.redirectToCollectionId(
response,
collectionId
);
}
async showEditPage({ auth, inertia, request, response }: HttpContext) {
const linkId = request.qs()?.linkId;
if (!linkId) {
return response.redirectToNamedRoute('dashboard');
}
const userId = auth.user!.id;
const collections =
await this.collectionsController.getCollectionsByAuthorId(userId);
const link = await this.getLinkById(linkId, userId);
return inertia.render('links/edit', { collections, link });
}
async update({ request, auth, response }: HttpContext) {
const { params, ...payload } =
await request.validateUsing(updateLinkValidator);
// Throw if invalid link id provided
await this.getLinkById(params.id, auth.user!.id);
await Link.updateOrCreate(
{
id: params.id,
},
payload
);
return response.redirectToNamedRoute('dashboard', {
qs: { collectionId: payload.collectionId },
});
}
async toggleFavorite({ request, auth, response }: HttpContext) {
const { params, favorite } = await request.validateUsing(
updateLinkFavoriteStatusValidator
);
// Throw if invalid link id provided
await this.getLinkById(params.id, auth.user!.id);
await Link.updateOrCreate(
{
id: params.id,
},
{ favorite }
);
return response.json({ status: 'ok' });
}
async showDeletePage({ auth, inertia, request, response }: HttpContext) {
const linkId = request.qs()?.linkId;
if (!linkId) {
return response.redirectToNamedRoute('dashboard');
}
const link = await this.getLinkById(linkId, auth.user!.id);
await link.load('collection');
return inertia.render('links/delete', { link });
}
async delete({ request, auth, response }: HttpContext) {
const { params } = await request.validateUsing(deleteLinkValidator);
const link = await this.getLinkById(params.id, auth.user!.id);
await link.delete();
return response.redirectToNamedRoute('dashboard', {
qs: { collectionId: link.id },
});
}
async getTotalLinksCount() {
const totalCount = await db.from('links').count('* as total');
return Number(totalCount[0].total);
}
/**
* Get link by id.
*
* /!\ Only return private link (create by the current user)
*/
private async getLinkById(id: Link['id'], userId: Link['id']) {
return await Link.query()
.where('id', id)
.andWhere('author_id', userId)
.firstOrFail();
}
}

View File

@@ -0,0 +1,19 @@
import { LinkService } from '#links/services/link_service';
import { updateLinkFavoriteStatusValidator } from '#links/validators/update_favorite_link_validator';
import { inject } from '@adonisjs/core';
import { HttpContext } from '@adonisjs/core/http';
@inject()
export default class ToggleFavoriteController {
constructor(private linkService: LinkService) {}
async toggleFavorite({ request, response }: HttpContext) {
const { params, favorite } = await request.validateUsing(
updateLinkFavoriteStatusValidator
);
await this.linkService.updateFavorite(params.id, favorite);
return response.json({ status: 'ok' });
}
}

View File

@@ -0,0 +1,34 @@
import { CollectionService } from '#collections/services/collection_service';
import { LinkService } from '#links/services/link_service';
import { updateLinkValidator } from '#links/validators/update_link_validator';
import { inject } from '@adonisjs/core';
import { HttpContext } from '@adonisjs/core/http';
@inject()
export default class UpdateLinkController {
constructor(
private linkService: LinkService,
private collectionsService: CollectionService
) {}
async render({ auth, inertia, request, response }: HttpContext) {
const linkId = request.qs()?.linkId;
if (!linkId) {
return response.redirectToNamedRoute('dashboard');
}
const collections =
await this.collectionsService.getCollectionsForAuthenticatedUser();
const link = await this.linkService.getLinkById(linkId, auth.user!.id);
return inertia.render('links/edit', { collections, link });
}
async execute({ request }: HttpContext) {
const { params, ...payload } =
await request.validateUsing(updateLinkValidator);
await this.linkService.updateLink(params.id, payload);
return this.collectionsService.redirectToCollectionId(payload.collectionId);
}
}

View File

@@ -1,6 +1,14 @@
import { middleware } from '#start/kernel';
import router from '@adonisjs/core/services/router';
const LinksController = () => import('#links/controllers/links_controller');
const CreateLinkController = () =>
import('#links/controllers/create_link_controller');
const DeleteLinkController = () =>
import('#links/controllers/delete_link_controller');
const UpdateLinkController = () =>
import('#links/controllers/update_link_controller');
const ToggleFavoriteController = () =>
import('#links/controllers/toggle_favorite_controller');
/**
* Routes for authenticated users
@@ -8,21 +16,21 @@ const LinksController = () => import('#links/controllers/links_controller');
router
.group(() => {
router
.get('/create', [LinksController, 'showCreatePage'])
.get('/create', [CreateLinkController, 'render'])
.as('link.create-form');
router.post('/', [LinksController, 'store']).as('link.create');
router.post('/', [CreateLinkController, 'execute']).as('link.create');
router.get('/edit', [LinksController, 'showEditPage']).as('link.edit-form');
router.put('/:id', [LinksController, 'update']).as('link.edit');
router.get('/edit', [UpdateLinkController, 'render']).as('link.edit-form');
router.put('/:id', [UpdateLinkController, 'execute']).as('link.edit');
router
.put('/:id/favorite', [LinksController, 'toggleFavorite'])
.put('/:id/favorite', [ToggleFavoriteController, 'toggleFavorite'])
.as('link.toggle-favorite');
router
.get('/delete', [LinksController, 'showDeletePage'])
.get('/delete', [DeleteLinkController, 'render'])
.as('link.delete-form');
router.delete('/:id', [LinksController, 'delete']).as('link.delete');
router.delete('/:id', [DeleteLinkController, 'execute']).as('link.delete');
})
.middleware([middleware.auth()])
.prefix('/links');

View File

@@ -0,0 +1,60 @@
import Link from '#links/models/link';
import { HttpContext } from '@adonisjs/core/http';
type CreateLinkPayload = {
name: string;
description?: string;
url: string;
favorite: boolean;
collectionId: number;
};
type UpdateLinkPayload = CreateLinkPayload;
export class LinkService {
createLink(payload: CreateLinkPayload) {
const context = this.getAuthContext();
return Link.create({
...payload,
authorId: context.auth.user!.id,
});
}
updateLink(id: number, payload: UpdateLinkPayload) {
const context = this.getAuthContext();
return Link.query()
.where('id', id)
.andWhere('author_id', context.auth.user!.id)
.update(payload);
}
deleteLink(id: number) {
const context = this.getAuthContext();
return Link.query()
.where('id', id)
.andWhere('author_id', context.auth.user!.id)
.delete();
}
async getLinkById(id: Link['id'], userId: Link['id']) {
return await Link.query()
.where('id', id)
.andWhere('author_id', userId)
.firstOrFail();
}
updateFavorite(id: number, favorite: boolean) {
return Link.query()
.where('id', id)
.andWhere('author_id', this.getAuthContext().auth.user!.id)
.update({ favorite });
}
getAuthContext() {
const context = HttpContext.getOrFail();
if (!context.auth.user || !context.auth.user.id) {
throw new Error('User not authenticated');
}
return context;
}
}