mirror of
https://github.com/Sonny93/my-links.git
synced 2025-12-10 15:35:35 +00:00
refactor: migrate from types to dto
This commit is contained in:
@@ -1,28 +1,10 @@
|
||||
import AuthController from '#controllers/auth/auth_controller';
|
||||
import LinksController from '#controllers/links/delete_link_controller';
|
||||
import User from '#models/user';
|
||||
import { UserWithCountersDto } from '#dtos/user_with_counters';
|
||||
import { CollectionService } from '#services/collections/collection_service';
|
||||
import { inject } from '@adonisjs/core';
|
||||
import { HttpContext } from '@adonisjs/core/http';
|
||||
|
||||
class UserWithRelationCountDto {
|
||||
constructor(private user: User) {}
|
||||
|
||||
toJson = () => ({
|
||||
id: this.user.id,
|
||||
email: this.user.email,
|
||||
fullname: this.user.name,
|
||||
avatarUrl: this.user.avatarUrl,
|
||||
isAdmin: this.user.isAdmin,
|
||||
createdAt: this.user.createdAt.toString(),
|
||||
updatedAt: this.user.updatedAt.toString(),
|
||||
lastSeenAt:
|
||||
this.user.lastSeenAt?.toString() ?? this.user.updatedAt.toString(),
|
||||
linksCount: Number(this.user.$extras.totalLinks),
|
||||
collectionsCount: Number(this.user.$extras.totalCollections),
|
||||
});
|
||||
}
|
||||
|
||||
@inject()
|
||||
export default class AdminController {
|
||||
constructor(
|
||||
@@ -38,7 +20,7 @@ export default class AdminController {
|
||||
await this.collectionService.getTotalCollectionsCount();
|
||||
|
||||
return inertia.render('admin/dashboard', {
|
||||
users: users.map((user) => new UserWithRelationCountDto(user).toJson()),
|
||||
users: UserWithCountersDto.fromArray(users),
|
||||
totalLinks: linksCount,
|
||||
totalCollections: collectionsCount,
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { CollectionWithLinksDto } from '#dtos/collection_with_links';
|
||||
import { CollectionService } from '#services/collections/collection_service';
|
||||
import { createCollectionValidator } from '#validators/collections/create_collection_validator';
|
||||
import { inject } from '@adonisjs/core';
|
||||
@@ -8,17 +9,15 @@ export default class CreateCollectionController {
|
||||
constructor(private collectionService: CollectionService) {}
|
||||
|
||||
async execute({ request, response }: HttpContext) {
|
||||
console.log('avant');
|
||||
const payload = await request.validateUsing(createCollectionValidator);
|
||||
const collection = await this.collectionService.createCollection({
|
||||
name: payload.name,
|
||||
description: payload.description,
|
||||
visibility: payload.visibility,
|
||||
});
|
||||
console.log('après', collection);
|
||||
return response.json({
|
||||
message: 'Collection created successfully',
|
||||
collection: collection.serialize(),
|
||||
collection: new CollectionWithLinksDto(collection).serialize(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { CollectionWithLinksDto } from '#dtos/collection_with_links';
|
||||
import { CollectionService } from '#services/collections/collection_service';
|
||||
import { inject } from '@adonisjs/core';
|
||||
import { HttpContext } from '@adonisjs/core/http';
|
||||
@@ -10,7 +11,7 @@ export default class GetCollectionsController {
|
||||
const collections =
|
||||
await this.collectionService.getCollectionsForAuthenticatedUser();
|
||||
return response.json({
|
||||
collections: collections.map((collection) => collection.serialize()),
|
||||
collections: CollectionWithLinksDto.fromArray(collections),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { LinkDto } from '#dtos/link';
|
||||
import { LinkService } from '#services/links/link_service';
|
||||
import { createLinkValidator } from '#validators/links/create_link_validator';
|
||||
import { inject } from '@adonisjs/core';
|
||||
@@ -17,7 +18,7 @@ export default class CreateLinkController {
|
||||
});
|
||||
return response.json({
|
||||
message: 'Link created successfully',
|
||||
link: link.serialize(),
|
||||
link: new LinkDto(link).serialize(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { LinkWithCollectionDto } from '#dtos/link_with_collection';
|
||||
import { CollectionService } from '#services/collections/collection_service';
|
||||
import { LinkService } from '#services/links/link_service';
|
||||
import { deleteLinkValidator } from '#validators/links/delete_link_validator';
|
||||
@@ -20,7 +21,9 @@ export default class DeleteLinkController {
|
||||
|
||||
const link = await this.linkService.getLinkById(linkId, auth.user!.id);
|
||||
await link.load('collection');
|
||||
return inertia.render('links/delete', { link });
|
||||
return inertia.render('links/delete', {
|
||||
link: new LinkWithCollectionDto(link).serialize(),
|
||||
});
|
||||
}
|
||||
|
||||
async execute({ request, auth }: HttpContext) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { LinkDto } from '#dtos/link';
|
||||
import { LinkService } from '#services/links/link_service';
|
||||
import { inject } from '@adonisjs/core';
|
||||
import { HttpContext } from '@adonisjs/core/http';
|
||||
@@ -8,6 +9,6 @@ export default class GetFavoriteLinksController {
|
||||
|
||||
public async execute({ response }: HttpContext) {
|
||||
const links = await this.linkService.getFavoriteLinksForAuthenticatedUser();
|
||||
return response.json(links);
|
||||
return response.json(LinkDto.fromArray(links));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { CollectionDto } from '#dtos/collection';
|
||||
import { CollectionService } from '#services/collections/collection_service';
|
||||
import { deleteCollectionValidator } from '#validators/collections/delete_collection_validator';
|
||||
import { inject } from '@adonisjs/core';
|
||||
@@ -14,7 +15,7 @@ export default class DeleteCollectionController {
|
||||
const collection =
|
||||
await this.collectionService.getCollectionById(collectionId);
|
||||
return inertia.render('collections/delete', {
|
||||
collection,
|
||||
collection: new CollectionDto(collection).serialize(),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { CollectionDto } from '#dtos/collection';
|
||||
import { LinkDto } from '#dtos/link';
|
||||
import { CollectionService } from '#services/collections/collection_service';
|
||||
import { LinkService } from '#services/links/link_service';
|
||||
import { inject } from '@adonisjs/core';
|
||||
@@ -28,9 +30,11 @@ export default class ShowCollectionsController {
|
||||
}
|
||||
|
||||
return inertia.render('dashboard', {
|
||||
collections: collections.map((collection) => collection.serialize()),
|
||||
favoriteLinks: favoriteLinks.map((link) => link.serialize()),
|
||||
activeCollection: activeCollection?.serialize(),
|
||||
collections: CollectionDto.fromArray(collections),
|
||||
favoriteLinks: LinkDto.fromArray(favoriteLinks),
|
||||
activeCollection: activeCollection
|
||||
? new CollectionDto(activeCollection).serialize()
|
||||
: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { CollectionDto } from '#dtos/collection';
|
||||
import { CollectionService } from '#services/collections/collection_service';
|
||||
import { updateCollectionValidator } from '#validators/collections/update_collection_validator';
|
||||
import { inject } from '@adonisjs/core';
|
||||
@@ -14,7 +15,7 @@ export default class UpdateCollectionController {
|
||||
const collection =
|
||||
await this.collectionService.getCollectionById(collectionId);
|
||||
return inertia.render('collections/edit', {
|
||||
collection: collection.serialize(),
|
||||
collection: new CollectionDto(collection).serialize(),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { CollectionDto } from '#dtos/collection';
|
||||
import { CollectionService } from '#services/collections/collection_service';
|
||||
import { LinkService } from '#services/links/link_service';
|
||||
import { createLinkValidator } from '#validators/links/create_link_validator';
|
||||
@@ -14,7 +15,9 @@ export default class CreateLinkController {
|
||||
async render({ inertia }: HttpContext) {
|
||||
const collections =
|
||||
await this.collectionsService.getCollectionsForAuthenticatedUser();
|
||||
return inertia.render('links/create', { collections });
|
||||
return inertia.render('links/create', {
|
||||
collections: CollectionDto.fromArray(collections),
|
||||
});
|
||||
}
|
||||
|
||||
async execute({ request }: HttpContext) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { LinkWithCollectionDto } from '#dtos/link_with_collection';
|
||||
import { CollectionService } from '#services/collections/collection_service';
|
||||
import { LinkService } from '#services/links/link_service';
|
||||
import { deleteLinkValidator } from '#validators/links/delete_link_validator';
|
||||
@@ -20,7 +21,9 @@ export default class DeleteLinkController {
|
||||
|
||||
const link = await this.linkService.getLinkById(linkId, auth.user!.id);
|
||||
await link.load('collection');
|
||||
return inertia.render('links/delete', { link });
|
||||
return inertia.render('links/delete', {
|
||||
link: new LinkWithCollectionDto(link).serialize(),
|
||||
});
|
||||
}
|
||||
|
||||
async execute({ request, auth }: HttpContext) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { CollectionDto } from '#dtos/collection';
|
||||
import { LinkDto } from '#dtos/link';
|
||||
import { CollectionService } from '#services/collections/collection_service';
|
||||
import { LinkService } from '#services/links/link_service';
|
||||
import { updateLinkValidator } from '#validators/links/update_link_validator';
|
||||
@@ -21,7 +23,10 @@ export default class UpdateLinkController {
|
||||
await this.collectionsService.getCollectionsForAuthenticatedUser();
|
||||
const link = await this.linkService.getLinkById(linkId, auth.user!.id);
|
||||
|
||||
return inertia.render('links/edit', { collections, link });
|
||||
return inertia.render('links/edit', {
|
||||
collections: CollectionDto.fromArray(collections),
|
||||
link: new LinkDto(link).serialize(),
|
||||
});
|
||||
}
|
||||
|
||||
async execute({ request }: HttpContext) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { SharedCollectionDto } from '#dtos/shared_collection';
|
||||
import { CollectionService } from '#services/collections/collection_service';
|
||||
import { getSharedCollectionValidator } from '#validators/shared_collections/shared_collection';
|
||||
import { inject } from '@adonisjs/core';
|
||||
@@ -14,6 +15,8 @@ export default class SharedCollectionsController {
|
||||
|
||||
const activeCollection =
|
||||
await this.collectionService.getPublicCollectionById(params.id);
|
||||
return inertia.render('shared', { activeCollection });
|
||||
return inertia.render('shared', {
|
||||
activeCollection: new SharedCollectionDto(activeCollection).serialize(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ export default class DisplayPreferencesController {
|
||||
getDisplayPreferences().collectionListDisplay,
|
||||
};
|
||||
auth.user!.displayPreferences = mergedPrefs;
|
||||
console.log(auth.user!.displayPreferences);
|
||||
await auth.user!.save();
|
||||
return response.redirect().withQs().back();
|
||||
}
|
||||
|
||||
47
app/dtos/collection.ts
Normal file
47
app/dtos/collection.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { CommonModelDto } from '#dtos/common_model';
|
||||
import { Visibility } from '#enums/collections/visibility';
|
||||
import Collection from '#models/collection';
|
||||
|
||||
export class CollectionDto extends CommonModelDto<Collection> {
|
||||
declare id: number;
|
||||
declare name: string;
|
||||
declare description: string | null;
|
||||
declare visibility: Visibility;
|
||||
declare authorId: number;
|
||||
declare createdAt: string | null;
|
||||
declare updatedAt: string | null;
|
||||
|
||||
constructor(collection?: Collection) {
|
||||
if (!collection) return;
|
||||
super(collection);
|
||||
|
||||
this.id = collection.id;
|
||||
this.name = collection.name;
|
||||
this.description = collection.description;
|
||||
this.visibility = collection.visibility;
|
||||
this.authorId = collection.authorId;
|
||||
this.createdAt = collection.createdAt?.toISO();
|
||||
this.updatedAt = collection.updatedAt?.toISO();
|
||||
}
|
||||
|
||||
serialize(): {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string | null;
|
||||
visibility: Visibility;
|
||||
authorId: number;
|
||||
createdAt: string | null;
|
||||
updatedAt: string | null;
|
||||
} {
|
||||
return {
|
||||
...super.serialize(),
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
visibility: this.visibility,
|
||||
authorId: this.authorId,
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt,
|
||||
};
|
||||
}
|
||||
}
|
||||
53
app/dtos/collection_with_links.ts
Normal file
53
app/dtos/collection_with_links.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { CommonModelDto } from '#dtos/common_model';
|
||||
import { LinkDto } from '#dtos/link';
|
||||
import { Visibility } from '#enums/collections/visibility';
|
||||
import Collection from '#models/collection';
|
||||
import { Link } from '#shared/types/dto';
|
||||
|
||||
export class CollectionWithLinksDto extends CommonModelDto<Collection> {
|
||||
declare id: number;
|
||||
declare name: string;
|
||||
declare description: string | null;
|
||||
declare visibility: Visibility;
|
||||
declare authorId: number;
|
||||
declare links: LinkDto[];
|
||||
declare createdAt: string | null;
|
||||
declare updatedAt: string | null;
|
||||
|
||||
constructor(collection?: Collection) {
|
||||
if (!collection) return;
|
||||
super(collection);
|
||||
|
||||
this.id = collection.id;
|
||||
this.name = collection.name;
|
||||
this.description = collection.description;
|
||||
this.visibility = collection.visibility;
|
||||
this.authorId = collection.authorId;
|
||||
this.links = LinkDto.fromArray(collection.links);
|
||||
this.createdAt = collection.createdAt?.toISO();
|
||||
this.updatedAt = collection.updatedAt?.toISO();
|
||||
}
|
||||
|
||||
serialize(): {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string | null;
|
||||
visibility: Visibility;
|
||||
authorId: number;
|
||||
links: Link[];
|
||||
createdAt: string | null;
|
||||
updatedAt: string | null;
|
||||
} {
|
||||
return {
|
||||
...super.serialize(),
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
visibility: this.visibility,
|
||||
authorId: this.authorId,
|
||||
links: this.links.map((link) => link.serialize()),
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt,
|
||||
};
|
||||
}
|
||||
}
|
||||
57
app/dtos/common_model.ts
Normal file
57
app/dtos/common_model.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import SimplePaginatorDto from '#dtos/simple_paginator';
|
||||
import AppBaseModel from '#models/app_base_model';
|
||||
import { SimplePaginatorDtoMetaRange, StaticDto } from '#types/dto';
|
||||
import { LucidRow, ModelPaginatorContract } from '@adonisjs/lucid/types/model';
|
||||
import { SimplePaginatorContract } from '@adonisjs/lucid/types/querybuilder';
|
||||
|
||||
export abstract class CommonModelDto<T extends AppBaseModel> {
|
||||
declare id: number;
|
||||
declare createdAt: string | null;
|
||||
declare updatedAt: string | null;
|
||||
|
||||
constructor(model?: T) {
|
||||
if (!model) return;
|
||||
this.id = model.id;
|
||||
this.createdAt = model.createdAt?.toISO();
|
||||
this.updatedAt = model.updatedAt?.toISO();
|
||||
}
|
||||
|
||||
static fromArray<
|
||||
T extends AppBaseModel,
|
||||
TDto extends CommonModelDto<T>,
|
||||
TModel = any,
|
||||
>(
|
||||
this: new (model: TModel, ...args: any[]) => TDto,
|
||||
models: TModel[],
|
||||
...args: any[]
|
||||
): TDto[] {
|
||||
if (!Array.isArray(models)) return [];
|
||||
return models.map((model) => new this(model, ...args));
|
||||
}
|
||||
|
||||
static fromPaginator<
|
||||
T extends AppBaseModel,
|
||||
TDto extends CommonModelDto<T>,
|
||||
TModel = any,
|
||||
>(
|
||||
this: StaticDto<TModel, TDto>,
|
||||
paginator: TModel extends LucidRow
|
||||
? ModelPaginatorContract<TModel>
|
||||
: SimplePaginatorContract<TModel>,
|
||||
range?: SimplePaginatorDtoMetaRange
|
||||
) {
|
||||
return new SimplePaginatorDto(paginator, this, range);
|
||||
}
|
||||
|
||||
serialize(): {
|
||||
id: number;
|
||||
createdAt: string | null;
|
||||
updatedAt: string | null;
|
||||
} {
|
||||
return {
|
||||
id: this.id,
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt,
|
||||
};
|
||||
}
|
||||
}
|
||||
54
app/dtos/link.ts
Normal file
54
app/dtos/link.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { CommonModelDto } from '#dtos/common_model';
|
||||
import Link from '#models/link';
|
||||
|
||||
export class LinkDto extends CommonModelDto<Link> {
|
||||
declare id: number;
|
||||
declare name: string;
|
||||
declare description: string | null;
|
||||
declare url: string;
|
||||
declare favorite: boolean;
|
||||
declare collectionId: number;
|
||||
declare authorId: number;
|
||||
declare createdAt: string | null;
|
||||
declare updatedAt: string | null;
|
||||
|
||||
constructor(link?: Link) {
|
||||
if (!link) return;
|
||||
super(link);
|
||||
|
||||
this.id = link.id;
|
||||
this.name = link.name;
|
||||
this.description = link.description;
|
||||
this.url = link.url;
|
||||
this.favorite = link.favorite;
|
||||
this.collectionId = link.collectionId;
|
||||
this.authorId = link.authorId;
|
||||
this.createdAt = link.createdAt?.toISO();
|
||||
this.updatedAt = link.updatedAt?.toISO();
|
||||
}
|
||||
|
||||
serialize(): {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string | null;
|
||||
url: string;
|
||||
favorite: boolean;
|
||||
collectionId: number;
|
||||
authorId: number;
|
||||
createdAt: string | null;
|
||||
updatedAt: string | null;
|
||||
} {
|
||||
return {
|
||||
...super.serialize(),
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
url: this.url,
|
||||
favorite: this.favorite,
|
||||
collectionId: this.collectionId,
|
||||
authorId: this.authorId,
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt,
|
||||
};
|
||||
}
|
||||
}
|
||||
60
app/dtos/link_with_collection.ts
Normal file
60
app/dtos/link_with_collection.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { CollectionDto } from '#dtos/collection';
|
||||
import { CommonModelDto } from '#dtos/common_model';
|
||||
import Link from '#models/link';
|
||||
import { Collection } from '#shared/types/dto';
|
||||
|
||||
export class LinkWithCollectionDto extends CommonModelDto<Link> {
|
||||
declare id: number;
|
||||
declare name: string;
|
||||
declare description: string | null;
|
||||
declare url: string;
|
||||
declare favorite: boolean;
|
||||
declare collectionId: number;
|
||||
declare collection: CollectionDto;
|
||||
declare authorId: number;
|
||||
declare createdAt: string | null;
|
||||
declare updatedAt: string | null;
|
||||
|
||||
constructor(link?: Link) {
|
||||
if (!link) return;
|
||||
super(link);
|
||||
|
||||
this.id = link.id;
|
||||
this.name = link.name;
|
||||
this.description = link.description;
|
||||
this.url = link.url;
|
||||
this.favorite = link.favorite;
|
||||
this.collectionId = link.collectionId;
|
||||
this.collection = new CollectionDto(link.collection);
|
||||
this.authorId = link.authorId;
|
||||
this.createdAt = link.createdAt?.toISO();
|
||||
this.updatedAt = link.updatedAt?.toISO();
|
||||
}
|
||||
|
||||
serialize(): {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string | null;
|
||||
url: string;
|
||||
favorite: boolean;
|
||||
collectionId: number;
|
||||
collection: Collection;
|
||||
authorId: number;
|
||||
createdAt: string | null;
|
||||
updatedAt: string | null;
|
||||
} {
|
||||
return {
|
||||
...super.serialize(),
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
url: this.url,
|
||||
favorite: this.favorite,
|
||||
collectionId: this.collectionId,
|
||||
collection: this.collection.serialize(),
|
||||
authorId: this.authorId,
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt,
|
||||
};
|
||||
}
|
||||
}
|
||||
58
app/dtos/shared_collection.ts
Normal file
58
app/dtos/shared_collection.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { CommonModelDto } from '#dtos/common_model';
|
||||
import { LinkDto } from '#dtos/link';
|
||||
import { UserDto } from '#dtos/user';
|
||||
import { Visibility } from '#enums/collections/visibility';
|
||||
import Collection from '#models/collection';
|
||||
import { Link, User } from '#shared/types/dto';
|
||||
|
||||
export class SharedCollectionDto extends CommonModelDto<Collection> {
|
||||
declare id: number;
|
||||
declare name: string;
|
||||
declare description: string | null;
|
||||
declare visibility: Visibility;
|
||||
declare links: LinkDto[];
|
||||
declare authorId: number;
|
||||
declare author: UserDto;
|
||||
declare createdAt: string | null;
|
||||
declare updatedAt: string | null;
|
||||
|
||||
constructor(collection?: Collection) {
|
||||
if (!collection) return;
|
||||
super(collection);
|
||||
|
||||
this.id = collection.id;
|
||||
this.name = collection.name;
|
||||
this.description = collection.description;
|
||||
this.visibility = collection.visibility;
|
||||
this.links = LinkDto.fromArray(collection.links);
|
||||
this.authorId = collection.authorId;
|
||||
this.author = new UserDto(collection.author);
|
||||
this.createdAt = collection.createdAt?.toISO();
|
||||
this.updatedAt = collection.updatedAt?.toISO();
|
||||
}
|
||||
|
||||
serialize(): {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string | null;
|
||||
visibility: Visibility;
|
||||
links: Link[];
|
||||
authorId: number;
|
||||
author: User;
|
||||
createdAt: string | null;
|
||||
updatedAt: string | null;
|
||||
} {
|
||||
return {
|
||||
...super.serialize(),
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
visibility: this.visibility,
|
||||
links: this.links.map((link) => link.serialize()),
|
||||
authorId: this.authorId,
|
||||
author: this.author.serialize(),
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt,
|
||||
};
|
||||
}
|
||||
}
|
||||
65
app/dtos/simple_paginator.ts
Normal file
65
app/dtos/simple_paginator.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
// Source : https://github.com/adocasts/package-dto/blob/main/src/paginator/simple_paginator_dto.ts
|
||||
|
||||
import { CommonModelDto } from '#dtos/common_model';
|
||||
import AppBaseModel from '#models/app_base_model';
|
||||
import {
|
||||
SimplePaginatorDtoContract,
|
||||
SimplePaginatorDtoMetaContract,
|
||||
SimplePaginatorDtoMetaRange,
|
||||
StaticDto,
|
||||
} from '#types/dto';
|
||||
import { LucidRow, ModelPaginatorContract } from '@adonisjs/lucid/types/model';
|
||||
import { SimplePaginatorContract } from '@adonisjs/lucid/types/querybuilder';
|
||||
|
||||
export default class SimplePaginatorDto<
|
||||
T extends AppBaseModel,
|
||||
TDto extends CommonModelDto<T>,
|
||||
TModel = any,
|
||||
> implements SimplePaginatorDtoContract<TDto>
|
||||
{
|
||||
declare data: TDto[];
|
||||
declare meta: SimplePaginatorDtoMetaContract;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of the SimplePaginatorDto class.
|
||||
*
|
||||
* @param {SimplePaginatorContract<Model>|ModelPaginatorContract<Model>} paginator - The paginator object containing the data.
|
||||
* @param {StaticDto<Model, Dto>} dto - The static DTO class used to map the data.
|
||||
* @param {SimplePaginatorDtoMetaRange} [range] - Optional range for the paginator.
|
||||
*/
|
||||
constructor(
|
||||
paginator: TModel extends LucidRow
|
||||
? ModelPaginatorContract<TModel>
|
||||
: SimplePaginatorContract<TModel>,
|
||||
dto: StaticDto<TModel, TDto>,
|
||||
range?: SimplePaginatorDtoMetaRange
|
||||
) {
|
||||
this.data = paginator.all().map((row) => new dto(row));
|
||||
|
||||
this.meta = {
|
||||
total: paginator.total,
|
||||
perPage: paginator.perPage,
|
||||
currentPage: paginator.currentPage,
|
||||
lastPage: paginator.lastPage,
|
||||
firstPage: paginator.firstPage,
|
||||
firstPageUrl: paginator.getUrl(1),
|
||||
lastPageUrl: paginator.getUrl(paginator.lastPage),
|
||||
nextPageUrl: paginator.getNextPageUrl(),
|
||||
previousPageUrl: paginator.getPreviousPageUrl(),
|
||||
};
|
||||
|
||||
if (range?.start || range?.end) {
|
||||
const start = range?.start || paginator.firstPage;
|
||||
const end = range?.end || paginator.lastPage;
|
||||
|
||||
this.meta.pagesInRange = paginator.getUrlsForRange(start, end);
|
||||
}
|
||||
}
|
||||
|
||||
serialize() {
|
||||
return {
|
||||
data: this.data.map((item) => item.serialize()),
|
||||
meta: this.meta,
|
||||
};
|
||||
}
|
||||
}
|
||||
42
app/dtos/user.ts
Normal file
42
app/dtos/user.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { CommonModelDto } from '#dtos/common_model';
|
||||
import User from '#models/user';
|
||||
|
||||
export class UserDto extends CommonModelDto<User> {
|
||||
declare id: number;
|
||||
declare fullname: string;
|
||||
declare avatarUrl: string;
|
||||
declare isAdmin: boolean;
|
||||
declare createdAt: string | null;
|
||||
declare updatedAt: string | null;
|
||||
|
||||
constructor(user?: User) {
|
||||
if (!user) return;
|
||||
super(user);
|
||||
|
||||
this.id = user.id;
|
||||
this.fullname = user.fullname;
|
||||
this.avatarUrl = user.avatarUrl;
|
||||
this.isAdmin = user.isAdmin;
|
||||
this.createdAt = user.createdAt.toISO();
|
||||
this.updatedAt = user.updatedAt.toISO();
|
||||
}
|
||||
|
||||
serialize(): {
|
||||
id: number;
|
||||
fullname: string;
|
||||
avatarUrl: string;
|
||||
isAdmin: boolean;
|
||||
createdAt: string | null;
|
||||
updatedAt: string | null;
|
||||
} {
|
||||
return {
|
||||
...super.serialize(),
|
||||
id: this.id,
|
||||
fullname: this.fullname,
|
||||
avatarUrl: this.avatarUrl,
|
||||
isAdmin: this.isAdmin,
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt,
|
||||
};
|
||||
}
|
||||
}
|
||||
27
app/dtos/user_auth.ts
Normal file
27
app/dtos/user_auth.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { UserDto } from '#dtos/user';
|
||||
import User from '#models/user';
|
||||
|
||||
export class UserAuthDto {
|
||||
declare isAuthenticated: boolean;
|
||||
declare isAdmin: boolean;
|
||||
declare user?: UserDto;
|
||||
|
||||
constructor(user: User | undefined) {
|
||||
if (!user) return;
|
||||
this.isAuthenticated = !!user;
|
||||
this.isAdmin = user?.isAdmin;
|
||||
this.user = user && new UserDto(user);
|
||||
}
|
||||
|
||||
serialize(): {
|
||||
isAuthenticated: boolean;
|
||||
isAdmin: boolean;
|
||||
user: ReturnType<UserDto['serialize']> | undefined;
|
||||
} {
|
||||
return {
|
||||
isAuthenticated: this.isAuthenticated,
|
||||
isAdmin: this.isAdmin,
|
||||
user: this.user?.serialize(),
|
||||
};
|
||||
}
|
||||
}
|
||||
58
app/dtos/user_with_counters.ts
Normal file
58
app/dtos/user_with_counters.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { CommonModelDto } from '#dtos/common_model';
|
||||
import User from '#models/user';
|
||||
|
||||
export class UserWithCountersDto extends CommonModelDto<User> {
|
||||
declare id: number;
|
||||
declare email: string;
|
||||
declare fullname: string;
|
||||
declare avatarUrl: string;
|
||||
declare isAdmin: boolean;
|
||||
declare linksCount: number;
|
||||
declare collectionsCount: number;
|
||||
declare lastSeenAt: string | null;
|
||||
declare createdAt: string | null;
|
||||
declare updatedAt: string | null;
|
||||
|
||||
constructor(user?: User) {
|
||||
if (!user) return;
|
||||
super(user);
|
||||
|
||||
this.id = user.id;
|
||||
this.email = user.email;
|
||||
this.fullname = user.fullname;
|
||||
this.avatarUrl = user.avatarUrl;
|
||||
this.isAdmin = user.isAdmin;
|
||||
this.linksCount = Number(user.$extras.totalLinks);
|
||||
this.collectionsCount = Number(user.$extras.totalCollections);
|
||||
this.lastSeenAt = user.lastSeenAt?.toString() ?? user.updatedAt.toString();
|
||||
this.createdAt = user.createdAt?.toISO();
|
||||
this.updatedAt = user.updatedAt?.toISO();
|
||||
}
|
||||
|
||||
serialize(): {
|
||||
id: number;
|
||||
email: string;
|
||||
fullname: string;
|
||||
avatarUrl: string;
|
||||
isAdmin: boolean;
|
||||
linksCount: number;
|
||||
collectionsCount: number;
|
||||
lastSeenAt: string | null;
|
||||
createdAt: string | null;
|
||||
updatedAt: string | null;
|
||||
} {
|
||||
return {
|
||||
...super.serialize(),
|
||||
id: this.id,
|
||||
email: this.email,
|
||||
fullname: this.fullname,
|
||||
avatarUrl: this.avatarUrl,
|
||||
isAdmin: this.isAdmin,
|
||||
linksCount: this.linksCount,
|
||||
collectionsCount: this.collectionsCount,
|
||||
lastSeenAt: this.lastSeenAt,
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt,
|
||||
};
|
||||
}
|
||||
}
|
||||
28
app/types/dto.ts
Normal file
28
app/types/dto.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
export type StaticDto<Model, Dto> = { new (model: Model): Dto };
|
||||
|
||||
export interface SimplePaginatorDtoContract<Dto> {
|
||||
data: Dto[];
|
||||
meta: SimplePaginatorDtoMetaContract;
|
||||
}
|
||||
|
||||
export interface SimplePaginatorDtoMetaContract {
|
||||
total: number;
|
||||
perPage: number;
|
||||
currentPage: number;
|
||||
lastPage: number;
|
||||
firstPage: number;
|
||||
firstPageUrl: string;
|
||||
lastPageUrl: string;
|
||||
nextPageUrl: string | null;
|
||||
previousPageUrl: string | null;
|
||||
pagesInRange?: {
|
||||
url: string;
|
||||
page: number;
|
||||
isActive: boolean;
|
||||
}[];
|
||||
}
|
||||
|
||||
export type SimplePaginatorDtoMetaRange = {
|
||||
start: number;
|
||||
end: number;
|
||||
};
|
||||
@@ -1,10 +1,12 @@
|
||||
import { isSSREnableForPage } from '#config/ssr';
|
||||
import { DEFAULT_USER_THEME, KEY_USER_THEME } from '#constants/user/theme';
|
||||
import { UserAuthDto } from '#dtos/user_auth';
|
||||
import env from '#start/env';
|
||||
import logger from '@adonisjs/core/services/logger';
|
||||
import { defineConfig } from '@adonisjs/inertia';
|
||||
import type { InferSharedProps } from '@adonisjs/inertia/types';
|
||||
|
||||
export default defineConfig({
|
||||
const inertiaConfig = defineConfig({
|
||||
/**
|
||||
* Path to the Edge view that will be used as the root view for Inertia responses
|
||||
*/
|
||||
@@ -19,13 +21,11 @@ export default defineConfig({
|
||||
user: (ctx) => ({
|
||||
theme: ctx.session?.get(KEY_USER_THEME, DEFAULT_USER_THEME),
|
||||
}),
|
||||
auth: async (ctx) => {
|
||||
auth: async (ctx) =>
|
||||
ctx.inertia.always(async () => {
|
||||
await ctx.auth?.check();
|
||||
return {
|
||||
user: ctx.auth?.user || null,
|
||||
isAuthenticated: ctx.auth?.isAuthenticated || false,
|
||||
};
|
||||
},
|
||||
return new UserAuthDto(ctx.auth?.user).serialize();
|
||||
}),
|
||||
appUrl: env.get('APP_URL'),
|
||||
},
|
||||
|
||||
@@ -42,3 +42,9 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default inertiaConfig;
|
||||
|
||||
declare module '@adonisjs/inertia/types' {
|
||||
export interface SharedProps extends InferSharedProps<typeof inertiaConfig> {}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { UserWithCounters } from '#shared/types/dto';
|
||||
import {
|
||||
ScrollArea,
|
||||
Table,
|
||||
@@ -15,23 +16,16 @@ import { Th } from '~/components/admin/users/th';
|
||||
import { sortData } from '~/components/admin/users/utils';
|
||||
import { UserBadgeRole } from '~/components/common/user_badge_role';
|
||||
import { DATE_FORMAT } from '~/constants';
|
||||
import { User } from '~/types/app';
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
export type UserWithCounts = User & {
|
||||
linksCount: number;
|
||||
collectionsCount: number;
|
||||
};
|
||||
export type UsersWithCounts = UserWithCounts[];
|
||||
|
||||
export type Columns = keyof UserWithCounts;
|
||||
export type Columns = keyof UserWithCounters;
|
||||
|
||||
const DEFAULT_SORT_BY: Columns = 'lastSeenAt';
|
||||
const DEFAULT_SORT_DIRECTION = true;
|
||||
|
||||
export interface UsersTableProps {
|
||||
users: UsersWithCounts;
|
||||
users: UserWithCounters[];
|
||||
totalCollections: number;
|
||||
totalLinks: number;
|
||||
}
|
||||
@@ -56,7 +50,7 @@ export function UsersTable({
|
||||
})
|
||||
);
|
||||
|
||||
const setSorting = (field: keyof UserWithCounts) => {
|
||||
const setSorting = (field: keyof UserWithCounters) => {
|
||||
const reversed = field === sortBy ? !reverseSortDirection : false;
|
||||
setReverseSortDirection(reversed);
|
||||
setSortBy(field);
|
||||
@@ -75,11 +69,14 @@ export function UsersTable({
|
||||
);
|
||||
};
|
||||
|
||||
const renderDateCell = (date: string) => (
|
||||
const renderDateCell = (date: string | null) => {
|
||||
if (!date) return '-';
|
||||
return (
|
||||
<Tooltip label={dayjs(date).format(DATE_FORMAT).toString()}>
|
||||
<Text>{dayjs(date).fromNow()}</Text>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const rows = sortedData.map((user) => (
|
||||
<Table.Tr key={user.id}>
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
import {
|
||||
UsersWithCounts,
|
||||
UserWithCounts,
|
||||
} from '~/components/admin/users/users_table';
|
||||
import { UserWithCounters } from '#shared/types/dto';
|
||||
|
||||
export function filterData(data: UsersWithCounts, search: string) {
|
||||
export function filterData(data: UserWithCounters[], search: string) {
|
||||
const query = search.toLowerCase().trim();
|
||||
return data.filter((item) =>
|
||||
['email', 'name', 'nickName', 'fullname'].some((key) => {
|
||||
const value = item[key as keyof UserWithCounts];
|
||||
['email', 'fullname'].some((key) => {
|
||||
const value = item[key as keyof UserWithCounters];
|
||||
return typeof value === 'string' && value.toLowerCase().includes(query);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function sortData(
|
||||
data: UsersWithCounts,
|
||||
data: UserWithCounters[],
|
||||
payload: {
|
||||
sortBy: keyof UserWithCounts | null;
|
||||
sortBy: keyof UserWithCounters | null;
|
||||
reversed: boolean;
|
||||
search: string;
|
||||
}
|
||||
@@ -29,6 +26,7 @@ export function sortData(
|
||||
|
||||
return filterData(
|
||||
[...data].sort((a, b) => {
|
||||
if (!a[sortBy] || !b[sortBy]) return 0;
|
||||
if (payload.reversed) {
|
||||
return b[sortBy] > a[sortBy] ? 1 : -1;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { UserWithCounters } from '#shared/types/dto';
|
||||
import { Badge } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { PublicUser, User } from '~/types/app';
|
||||
|
||||
interface UserBadgeRoleProps {
|
||||
user: User | PublicUser;
|
||||
user: UserWithCounters;
|
||||
}
|
||||
|
||||
export function UserBadgeRole({ user }: UserBadgeRoleProps) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { CollectionWithLinks } from '#shared/types/dto';
|
||||
import { Link } from '@inertiajs/react';
|
||||
import { route } from '@izzyjs/route/client';
|
||||
import { Text } from '@mantine/core';
|
||||
@@ -5,7 +6,6 @@ import { useEffect, useRef } from 'react';
|
||||
import { AiFillFolderOpen, AiOutlineFolder } from 'react-icons/ai';
|
||||
import { useActiveCollection } from '~/hooks/collections/use_active_collection';
|
||||
import { appendCollectionId } from '~/lib/navigation';
|
||||
import { CollectionWithLinks } from '~/types/app';
|
||||
import classes from './collection_item.module.css';
|
||||
|
||||
interface CollectionItemProps {
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { LinkWithCollection } from '#shared/types/dto';
|
||||
import { Card, Flex, Group, Text } from '@mantine/core';
|
||||
import { ExternalLinkStyled } from '~/components/common/external_link_styled';
|
||||
import LinkFavicon from '~/components/dashboard/link/item/favicon/link_favicon';
|
||||
import LinkControls from '~/components/dashboard/link/item/link_controls';
|
||||
import { LinkWithCollection } from '~/types/app';
|
||||
import styles from './favorite_item.module.css';
|
||||
|
||||
export const FavoriteItem = ({ link }: { link: LinkWithCollection }) => (
|
||||
interface FavoriteItemProps {
|
||||
link: LinkWithCollection;
|
||||
}
|
||||
|
||||
export const FavoriteItem = ({ link }: FavoriteItemProps) => (
|
||||
<ExternalLinkStyled href={link.url} title={link.url}>
|
||||
<Card className={styles.linkWrapper}>
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Link } from '#shared/types/dto';
|
||||
import { Link as InertiaLink, router } from '@inertiajs/react';
|
||||
import { route } from '@izzyjs/route/client';
|
||||
import { ActionIcon, Menu } from '@mantine/core';
|
||||
@@ -10,10 +11,9 @@ import { IoTrashOutline } from 'react-icons/io5';
|
||||
import { MdFavorite, MdFavoriteBorder } from 'react-icons/md';
|
||||
import { onFavorite } from '~/lib/favorite';
|
||||
import { appendCollectionId, appendLinkId } from '~/lib/navigation';
|
||||
import { Link, PublicLink } from '~/types/app';
|
||||
|
||||
interface LinksControlsProps {
|
||||
link: Link | PublicLink;
|
||||
link: Link;
|
||||
showGoToCollection?: boolean;
|
||||
}
|
||||
export default function LinkControls({
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Link } from '#shared/types/dto';
|
||||
import { Card, Flex, Group, Text } from '@mantine/core';
|
||||
import { AiFillStar } from 'react-icons/ai';
|
||||
import { ExternalLinkStyled } from '~/components/common/external_link_styled';
|
||||
import LinkFavicon from '~/components/dashboard/link/item/favicon/link_favicon';
|
||||
import LinkControls from '~/components/dashboard/link/item/link_controls';
|
||||
import type { LinkListProps } from '~/components/dashboard/link/list/link_list';
|
||||
import { Link, PublicLink } from '~/types/app';
|
||||
import styles from './link.module.css';
|
||||
|
||||
interface LinkItemProps extends LinkListProps {
|
||||
link: Link | PublicLink;
|
||||
link: Link;
|
||||
}
|
||||
|
||||
export function LinkItem({ link, hideMenu: hideMenu = false }: LinkItemProps) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Collection } from '#shared/types/dto';
|
||||
import { Checkbox, Select, TextInput } from '@mantine/core';
|
||||
import { FormEvent } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import BackToDashboard from '~/components/common/navigation/back_to_dashboard';
|
||||
import useSearchParam from '~/hooks/use_search_param';
|
||||
import { FormLayout, FormLayoutProps } from '~/layouts/form_layout';
|
||||
import { Collection } from '~/types/app';
|
||||
|
||||
export type FormLinkData = {
|
||||
name: string;
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export const LS_LANG_KEY = 'language';
|
||||
export const GOOGLE_SEARCH_URL = 'https://google.com/search?q=';
|
||||
export const DATE_FORMAT = 'DD MMM YYYY (HH:mm)';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CollectionWithLinks } from '#shared/types/dto';
|
||||
import { PageProps } from '@adonisjs/inertia/types';
|
||||
import { usePage } from '@inertiajs/react';
|
||||
import { CollectionWithLinks } from '~/types/app';
|
||||
|
||||
interface UseActiveCollectionProps {
|
||||
activeCollection?: CollectionWithLinks;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CollectionWithLinks } from '#shared/types/dto';
|
||||
import { PageProps } from '@adonisjs/inertia/types';
|
||||
import { usePage } from '@inertiajs/react';
|
||||
import { CollectionWithLinks } from '~/types/app';
|
||||
|
||||
interface UseCollectionsProps {
|
||||
collections: CollectionWithLinks[];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { LinkWithCollection } from '#shared/types/dto';
|
||||
import { PageProps } from '@adonisjs/inertia/types';
|
||||
import { usePage } from '@inertiajs/react';
|
||||
import { LinkWithCollection } from '~/types/app';
|
||||
|
||||
interface UseFavoriteLinksProps {
|
||||
favoriteLinks: LinkWithCollection[];
|
||||
|
||||
@@ -1,25 +1,15 @@
|
||||
import { UserAuth } from '#shared/types/dto';
|
||||
import { PageProps } from '@adonisjs/inertia/types';
|
||||
import { usePage } from '@inertiajs/react';
|
||||
import type { Auth, InertiaPage } from '~/types/inertia';
|
||||
|
||||
export const useAuth = () => usePage<InertiaPage>().props.auth;
|
||||
|
||||
export const withAuth = <T extends object>(
|
||||
Component: React.ComponentType<T & { auth: Auth }>
|
||||
): React.ComponentType<Omit<T, 'auth'>> => {
|
||||
return (props: Omit<T, 'auth'>) => {
|
||||
const useAuth = (): UserAuth => usePage<PageProps>().props.auth as UserAuth;
|
||||
const withAuth = <T extends object>(
|
||||
Component: React.ComponentType<T & { auth: UserAuth }>
|
||||
) => {
|
||||
return (props: T) => {
|
||||
const auth = useAuth();
|
||||
return <Component {...(props as T)} auth={auth} />;
|
||||
return <Component {...props} auth={auth} />;
|
||||
};
|
||||
};
|
||||
|
||||
export const withAuthRequired = <T extends object>(
|
||||
Component: React.ComponentType<T & { auth: Auth }>
|
||||
): React.ComponentType<Omit<T, 'auth'>> => {
|
||||
return (props: Omit<T, 'auth'>) => {
|
||||
const auth = useAuth();
|
||||
if (!auth.isAuthenticated) {
|
||||
return null;
|
||||
}
|
||||
return <Component {...(props as T)} auth={auth} />;
|
||||
};
|
||||
};
|
||||
export { useAuth, withAuth };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Link } from '#shared/types/dto';
|
||||
import { route } from '@izzyjs/route/client';
|
||||
import { makeRequest } from '~/lib/request';
|
||||
import { Link } from '~/types/app';
|
||||
|
||||
export const onFavorite = (
|
||||
linkId: Link['id'],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Collection, CollectionWithLinks, Link } from '~/types/app';
|
||||
import { Collection, CollectionWithLinks, Link } from '#shared/types/dto';
|
||||
|
||||
export const appendCollectionId = (
|
||||
url: string,
|
||||
|
||||
@@ -8,11 +8,13 @@ import {
|
||||
} from '~/components/form/form_collection';
|
||||
import { Visibility } from '~/types/app';
|
||||
|
||||
interface CreateCollectionPageProps {
|
||||
disableHomeLink: boolean;
|
||||
}
|
||||
|
||||
export default function CreateCollectionPage({
|
||||
disableHomeLink,
|
||||
}: {
|
||||
disableHomeLink: boolean;
|
||||
}) {
|
||||
}: CreateCollectionPageProps) {
|
||||
const { t } = useTranslation('common');
|
||||
const { data, setData, submit, processing } = useForm<FormCollectionData>({
|
||||
name: '',
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Collection } from '#shared/types/dto';
|
||||
import { useForm } from '@inertiajs/react';
|
||||
import { route } from '@izzyjs/route/client';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -5,13 +6,14 @@ import {
|
||||
FormCollection,
|
||||
FormCollectionData,
|
||||
} from '~/components/form/form_collection';
|
||||
import { Collection } from '~/types/app';
|
||||
|
||||
interface DeleteCollectionPageProps {
|
||||
collection: Collection;
|
||||
}
|
||||
|
||||
export default function DeleteCollectionPage({
|
||||
collection,
|
||||
}: {
|
||||
collection: Collection;
|
||||
}) {
|
||||
}: DeleteCollectionPageProps) {
|
||||
const { t } = useTranslation('common');
|
||||
const { data, setData, submit, processing, errors } =
|
||||
useForm<FormCollectionData>({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Collection } from '#shared/types/dto';
|
||||
import { useForm } from '@inertiajs/react';
|
||||
import { route } from '@izzyjs/route/client';
|
||||
import { useMemo } from 'react';
|
||||
@@ -6,13 +7,14 @@ import {
|
||||
FormCollection,
|
||||
FormCollectionData,
|
||||
} from '~/components/form/form_collection';
|
||||
import { Collection } from '~/types/app';
|
||||
|
||||
interface EditCollectionPageProps {
|
||||
collection: Collection;
|
||||
}
|
||||
|
||||
export default function EditCollectionPage({
|
||||
collection,
|
||||
}: {
|
||||
collection: Collection;
|
||||
}) {
|
||||
}: EditCollectionPageProps) {
|
||||
const { t } = useTranslation('common');
|
||||
const { data, setData, submit, processing, errors } =
|
||||
useForm<FormCollectionData>({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Collection } from '#shared/types/dto';
|
||||
import { useForm } from '@inertiajs/react';
|
||||
import { route } from '@izzyjs/route/client';
|
||||
import { useMemo } from 'react';
|
||||
@@ -5,13 +6,12 @@ import { useTranslation } from 'react-i18next';
|
||||
import { FormLink } from '~/components/form/form_link';
|
||||
import useSearchParam from '~/hooks/use_search_param';
|
||||
import { isValidHttpUrl } from '~/lib/navigation';
|
||||
import { Collection } from '~/types/app';
|
||||
|
||||
export default function CreateLinkPage({
|
||||
collections,
|
||||
}: {
|
||||
interface CreateLinkPageProps {
|
||||
collections: Collection[];
|
||||
}) {
|
||||
}
|
||||
|
||||
export default function CreateLinkPage({ collections }: CreateLinkPageProps) {
|
||||
const { t } = useTranslation('common');
|
||||
const collectionId =
|
||||
Number(useSearchParam('collectionId')) ?? collections[0].id;
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { LinkWithCollection } from '#shared/types/dto';
|
||||
import { useForm } from '@inertiajs/react';
|
||||
import { route } from '@izzyjs/route/client';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FormLink } from '~/components/form/form_link';
|
||||
import { LinkWithCollection } from '~/types/app';
|
||||
|
||||
export default function DeleteLinkPage({ link }: { link: LinkWithCollection }) {
|
||||
interface DeleteLinkPageProps {
|
||||
link: LinkWithCollection;
|
||||
}
|
||||
|
||||
export default function DeleteLinkPage({ link }: DeleteLinkPageProps) {
|
||||
const { t } = useTranslation('common');
|
||||
const { data, setData, submit, processing } = useForm({
|
||||
name: link.name,
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import { Collection, LinkWithCollection } from '#shared/types/dto';
|
||||
import { useForm } from '@inertiajs/react';
|
||||
import { route } from '@izzyjs/route/client';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FormLink } from '~/components/form/form_link';
|
||||
import { isValidHttpUrl } from '~/lib/navigation';
|
||||
import { Collection, Link } from '~/types/app';
|
||||
|
||||
export default function EditLinkPage({
|
||||
collections,
|
||||
link,
|
||||
}: {
|
||||
interface EditLinkPageProps {
|
||||
collections: Collection[];
|
||||
link: Link;
|
||||
}) {
|
||||
link: LinkWithCollection;
|
||||
}
|
||||
|
||||
export default function EditLinkPage({ collections, link }: EditLinkPageProps) {
|
||||
const { t } = useTranslation('common');
|
||||
const { data, setData, submit, processing } = useForm({
|
||||
name: link.name,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { SharedCollection } from '#shared/types/dto';
|
||||
import { Flex, Text } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { LinkList } from '~/components/dashboard/link/list/link_list';
|
||||
import type { CollectionWithLinks, PublicUser } from '~/types/app';
|
||||
|
||||
interface SharedPageProps {
|
||||
activeCollection: CollectionWithLinks & { author: PublicUser };
|
||||
activeCollection: SharedCollection;
|
||||
}
|
||||
|
||||
export default function SharedPage({ activeCollection }: SharedPageProps) {
|
||||
|
||||
@@ -1,64 +1,3 @@
|
||||
import { DisplayPreferences } from '#shared/types/index';
|
||||
|
||||
type CommonBase = {
|
||||
id: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type User = CommonBase & {
|
||||
email: string;
|
||||
fullname: string;
|
||||
avatarUrl: string;
|
||||
isAdmin: boolean;
|
||||
lastSeenAt: string;
|
||||
displayPreferences: DisplayPreferences;
|
||||
};
|
||||
|
||||
export type PublicUser = Omit<User, 'email'>;
|
||||
|
||||
export type Users = User[];
|
||||
|
||||
export type UserWithCollections = User & {
|
||||
collections: Collection[];
|
||||
};
|
||||
|
||||
export type UserWithRelationCount = CommonBase & {
|
||||
email: string;
|
||||
fullname: string;
|
||||
avatarUrl: string;
|
||||
isAdmin: string;
|
||||
linksCount: number;
|
||||
collectionsCount: number;
|
||||
lastSeenAt: string;
|
||||
};
|
||||
|
||||
export type Link = CommonBase & {
|
||||
name: string;
|
||||
description: string | null;
|
||||
url: string;
|
||||
favorite: boolean;
|
||||
collectionId: number;
|
||||
};
|
||||
|
||||
export type LinkWithCollection = Link & {
|
||||
collection: Collection;
|
||||
};
|
||||
|
||||
export type PublicLink = Omit<Link, 'favorite'>;
|
||||
export type PublicLinkWithCollection = Omit<Link, 'favorite'>;
|
||||
|
||||
export type Collection = CommonBase & {
|
||||
name: string;
|
||||
description: string | null;
|
||||
visibility: Visibility;
|
||||
authorId: number;
|
||||
};
|
||||
|
||||
export type CollectionWithLinks = Collection & {
|
||||
links: Link[];
|
||||
};
|
||||
|
||||
export enum Visibility {
|
||||
PUBLIC = 'PUBLIC',
|
||||
PRIVATE = 'PRIVATE',
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"#adonis/api": "./.adonisjs/api.ts",
|
||||
"#config/*": "./config/*.js",
|
||||
"#controllers/*": "./app/controllers/*.js",
|
||||
"#dtos/*": "./app/dtos/*.js",
|
||||
"#models/*": "./app/models/*.js",
|
||||
"#routes/*": "./app/routes/*.js",
|
||||
"#services/*": "./app/services/*.js",
|
||||
|
||||
19
shared/types/dto.ts
Normal file
19
shared/types/dto.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { CollectionDto } from '#dtos/collection';
|
||||
import { CollectionWithLinksDto } from '#dtos/collection_with_links';
|
||||
import { LinkDto } from '#dtos/link';
|
||||
import { LinkWithCollectionDto } from '#dtos/link_with_collection';
|
||||
import { SharedCollectionDto } from '#dtos/shared_collection';
|
||||
import { UserDto } from '#dtos/user';
|
||||
import { UserAuthDto } from '#dtos/user_auth';
|
||||
import { UserWithCountersDto } from '#dtos/user_with_counters';
|
||||
|
||||
export type User = ReturnType<UserDto['serialize']>;
|
||||
export type UserAuth = ReturnType<UserAuthDto['serialize']>;
|
||||
export type UserWithCounters = ReturnType<UserWithCountersDto['serialize']>;
|
||||
export type Collection = ReturnType<CollectionDto['serialize']>;
|
||||
export type SharedCollection = ReturnType<SharedCollectionDto['serialize']>;
|
||||
export type CollectionWithLinks = ReturnType<
|
||||
CollectionWithLinksDto['serialize']
|
||||
>;
|
||||
export type Link = ReturnType<LinkDto['serialize']>;
|
||||
export type LinkWithCollection = ReturnType<LinkWithCollectionDto['serialize']>;
|
||||
Reference in New Issue
Block a user