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 AuthController from '#controllers/auth/auth_controller';
|
||||||
import LinksController from '#controllers/links/delete_link_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 { CollectionService } from '#services/collections/collection_service';
|
||||||
import { inject } from '@adonisjs/core';
|
import { inject } from '@adonisjs/core';
|
||||||
import { HttpContext } from '@adonisjs/core/http';
|
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()
|
@inject()
|
||||||
export default class AdminController {
|
export default class AdminController {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -38,7 +20,7 @@ export default class AdminController {
|
|||||||
await this.collectionService.getTotalCollectionsCount();
|
await this.collectionService.getTotalCollectionsCount();
|
||||||
|
|
||||||
return inertia.render('admin/dashboard', {
|
return inertia.render('admin/dashboard', {
|
||||||
users: users.map((user) => new UserWithRelationCountDto(user).toJson()),
|
users: UserWithCountersDto.fromArray(users),
|
||||||
totalLinks: linksCount,
|
totalLinks: linksCount,
|
||||||
totalCollections: collectionsCount,
|
totalCollections: collectionsCount,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { CollectionWithLinksDto } from '#dtos/collection_with_links';
|
||||||
import { CollectionService } from '#services/collections/collection_service';
|
import { CollectionService } from '#services/collections/collection_service';
|
||||||
import { createCollectionValidator } from '#validators/collections/create_collection_validator';
|
import { createCollectionValidator } from '#validators/collections/create_collection_validator';
|
||||||
import { inject } from '@adonisjs/core';
|
import { inject } from '@adonisjs/core';
|
||||||
@@ -8,17 +9,15 @@ export default class CreateCollectionController {
|
|||||||
constructor(private collectionService: CollectionService) {}
|
constructor(private collectionService: CollectionService) {}
|
||||||
|
|
||||||
async execute({ request, response }: HttpContext) {
|
async execute({ request, response }: HttpContext) {
|
||||||
console.log('avant');
|
|
||||||
const payload = await request.validateUsing(createCollectionValidator);
|
const payload = await request.validateUsing(createCollectionValidator);
|
||||||
const collection = await this.collectionService.createCollection({
|
const collection = await this.collectionService.createCollection({
|
||||||
name: payload.name,
|
name: payload.name,
|
||||||
description: payload.description,
|
description: payload.description,
|
||||||
visibility: payload.visibility,
|
visibility: payload.visibility,
|
||||||
});
|
});
|
||||||
console.log('après', collection);
|
|
||||||
return response.json({
|
return response.json({
|
||||||
message: 'Collection created successfully',
|
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 { CollectionService } from '#services/collections/collection_service';
|
||||||
import { inject } from '@adonisjs/core';
|
import { inject } from '@adonisjs/core';
|
||||||
import { HttpContext } from '@adonisjs/core/http';
|
import { HttpContext } from '@adonisjs/core/http';
|
||||||
@@ -10,7 +11,7 @@ export default class GetCollectionsController {
|
|||||||
const collections =
|
const collections =
|
||||||
await this.collectionService.getCollectionsForAuthenticatedUser();
|
await this.collectionService.getCollectionsForAuthenticatedUser();
|
||||||
return response.json({
|
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 { LinkService } from '#services/links/link_service';
|
||||||
import { createLinkValidator } from '#validators/links/create_link_validator';
|
import { createLinkValidator } from '#validators/links/create_link_validator';
|
||||||
import { inject } from '@adonisjs/core';
|
import { inject } from '@adonisjs/core';
|
||||||
@@ -17,7 +18,7 @@ export default class CreateLinkController {
|
|||||||
});
|
});
|
||||||
return response.json({
|
return response.json({
|
||||||
message: 'Link created successfully',
|
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 { CollectionService } from '#services/collections/collection_service';
|
||||||
import { LinkService } from '#services/links/link_service';
|
import { LinkService } from '#services/links/link_service';
|
||||||
import { deleteLinkValidator } from '#validators/links/delete_link_validator';
|
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);
|
const link = await this.linkService.getLinkById(linkId, auth.user!.id);
|
||||||
await link.load('collection');
|
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) {
|
async execute({ request, auth }: HttpContext) {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { LinkDto } from '#dtos/link';
|
||||||
import { LinkService } from '#services/links/link_service';
|
import { LinkService } from '#services/links/link_service';
|
||||||
import { inject } from '@adonisjs/core';
|
import { inject } from '@adonisjs/core';
|
||||||
import { HttpContext } from '@adonisjs/core/http';
|
import { HttpContext } from '@adonisjs/core/http';
|
||||||
@@ -8,6 +9,6 @@ export default class GetFavoriteLinksController {
|
|||||||
|
|
||||||
public async execute({ response }: HttpContext) {
|
public async execute({ response }: HttpContext) {
|
||||||
const links = await this.linkService.getFavoriteLinksForAuthenticatedUser();
|
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 { CollectionService } from '#services/collections/collection_service';
|
||||||
import { deleteCollectionValidator } from '#validators/collections/delete_collection_validator';
|
import { deleteCollectionValidator } from '#validators/collections/delete_collection_validator';
|
||||||
import { inject } from '@adonisjs/core';
|
import { inject } from '@adonisjs/core';
|
||||||
@@ -14,7 +15,7 @@ export default class DeleteCollectionController {
|
|||||||
const collection =
|
const collection =
|
||||||
await this.collectionService.getCollectionById(collectionId);
|
await this.collectionService.getCollectionById(collectionId);
|
||||||
return inertia.render('collections/delete', {
|
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 { CollectionService } from '#services/collections/collection_service';
|
||||||
import { LinkService } from '#services/links/link_service';
|
import { LinkService } from '#services/links/link_service';
|
||||||
import { inject } from '@adonisjs/core';
|
import { inject } from '@adonisjs/core';
|
||||||
@@ -28,9 +30,11 @@ export default class ShowCollectionsController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return inertia.render('dashboard', {
|
return inertia.render('dashboard', {
|
||||||
collections: collections.map((collection) => collection.serialize()),
|
collections: CollectionDto.fromArray(collections),
|
||||||
favoriteLinks: favoriteLinks.map((link) => link.serialize()),
|
favoriteLinks: LinkDto.fromArray(favoriteLinks),
|
||||||
activeCollection: activeCollection?.serialize(),
|
activeCollection: activeCollection
|
||||||
|
? new CollectionDto(activeCollection).serialize()
|
||||||
|
: null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { CollectionDto } from '#dtos/collection';
|
||||||
import { CollectionService } from '#services/collections/collection_service';
|
import { CollectionService } from '#services/collections/collection_service';
|
||||||
import { updateCollectionValidator } from '#validators/collections/update_collection_validator';
|
import { updateCollectionValidator } from '#validators/collections/update_collection_validator';
|
||||||
import { inject } from '@adonisjs/core';
|
import { inject } from '@adonisjs/core';
|
||||||
@@ -14,7 +15,7 @@ export default class UpdateCollectionController {
|
|||||||
const collection =
|
const collection =
|
||||||
await this.collectionService.getCollectionById(collectionId);
|
await this.collectionService.getCollectionById(collectionId);
|
||||||
return inertia.render('collections/edit', {
|
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 { CollectionService } from '#services/collections/collection_service';
|
||||||
import { LinkService } from '#services/links/link_service';
|
import { LinkService } from '#services/links/link_service';
|
||||||
import { createLinkValidator } from '#validators/links/create_link_validator';
|
import { createLinkValidator } from '#validators/links/create_link_validator';
|
||||||
@@ -14,7 +15,9 @@ export default class CreateLinkController {
|
|||||||
async render({ inertia }: HttpContext) {
|
async render({ inertia }: HttpContext) {
|
||||||
const collections =
|
const collections =
|
||||||
await this.collectionsService.getCollectionsForAuthenticatedUser();
|
await this.collectionsService.getCollectionsForAuthenticatedUser();
|
||||||
return inertia.render('links/create', { collections });
|
return inertia.render('links/create', {
|
||||||
|
collections: CollectionDto.fromArray(collections),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute({ request }: HttpContext) {
|
async execute({ request }: HttpContext) {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { LinkWithCollectionDto } from '#dtos/link_with_collection';
|
||||||
import { CollectionService } from '#services/collections/collection_service';
|
import { CollectionService } from '#services/collections/collection_service';
|
||||||
import { LinkService } from '#services/links/link_service';
|
import { LinkService } from '#services/links/link_service';
|
||||||
import { deleteLinkValidator } from '#validators/links/delete_link_validator';
|
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);
|
const link = await this.linkService.getLinkById(linkId, auth.user!.id);
|
||||||
await link.load('collection');
|
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) {
|
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 { CollectionService } from '#services/collections/collection_service';
|
||||||
import { LinkService } from '#services/links/link_service';
|
import { LinkService } from '#services/links/link_service';
|
||||||
import { updateLinkValidator } from '#validators/links/update_link_validator';
|
import { updateLinkValidator } from '#validators/links/update_link_validator';
|
||||||
@@ -21,7 +23,10 @@ export default class UpdateLinkController {
|
|||||||
await this.collectionsService.getCollectionsForAuthenticatedUser();
|
await this.collectionsService.getCollectionsForAuthenticatedUser();
|
||||||
const link = await this.linkService.getLinkById(linkId, auth.user!.id);
|
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) {
|
async execute({ request }: HttpContext) {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { SharedCollectionDto } from '#dtos/shared_collection';
|
||||||
import { CollectionService } from '#services/collections/collection_service';
|
import { CollectionService } from '#services/collections/collection_service';
|
||||||
import { getSharedCollectionValidator } from '#validators/shared_collections/shared_collection';
|
import { getSharedCollectionValidator } from '#validators/shared_collections/shared_collection';
|
||||||
import { inject } from '@adonisjs/core';
|
import { inject } from '@adonisjs/core';
|
||||||
@@ -14,6 +15,8 @@ export default class SharedCollectionsController {
|
|||||||
|
|
||||||
const activeCollection =
|
const activeCollection =
|
||||||
await this.collectionService.getPublicCollectionById(params.id);
|
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,
|
getDisplayPreferences().collectionListDisplay,
|
||||||
};
|
};
|
||||||
auth.user!.displayPreferences = mergedPrefs;
|
auth.user!.displayPreferences = mergedPrefs;
|
||||||
console.log(auth.user!.displayPreferences);
|
|
||||||
await auth.user!.save();
|
await auth.user!.save();
|
||||||
return response.redirect().withQs().back();
|
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 { isSSREnableForPage } from '#config/ssr';
|
||||||
import { DEFAULT_USER_THEME, KEY_USER_THEME } from '#constants/user/theme';
|
import { DEFAULT_USER_THEME, KEY_USER_THEME } from '#constants/user/theme';
|
||||||
|
import { UserAuthDto } from '#dtos/user_auth';
|
||||||
import env from '#start/env';
|
import env from '#start/env';
|
||||||
import logger from '@adonisjs/core/services/logger';
|
import logger from '@adonisjs/core/services/logger';
|
||||||
import { defineConfig } from '@adonisjs/inertia';
|
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
|
* 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) => ({
|
user: (ctx) => ({
|
||||||
theme: ctx.session?.get(KEY_USER_THEME, DEFAULT_USER_THEME),
|
theme: ctx.session?.get(KEY_USER_THEME, DEFAULT_USER_THEME),
|
||||||
}),
|
}),
|
||||||
auth: async (ctx) => {
|
auth: async (ctx) =>
|
||||||
await ctx.auth?.check();
|
ctx.inertia.always(async () => {
|
||||||
return {
|
await ctx.auth?.check();
|
||||||
user: ctx.auth?.user || null,
|
return new UserAuthDto(ctx.auth?.user).serialize();
|
||||||
isAuthenticated: ctx.auth?.isAuthenticated || false,
|
}),
|
||||||
};
|
|
||||||
},
|
|
||||||
appUrl: env.get('APP_URL'),
|
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 {
|
import {
|
||||||
ScrollArea,
|
ScrollArea,
|
||||||
Table,
|
Table,
|
||||||
@@ -15,23 +16,16 @@ import { Th } from '~/components/admin/users/th';
|
|||||||
import { sortData } from '~/components/admin/users/utils';
|
import { sortData } from '~/components/admin/users/utils';
|
||||||
import { UserBadgeRole } from '~/components/common/user_badge_role';
|
import { UserBadgeRole } from '~/components/common/user_badge_role';
|
||||||
import { DATE_FORMAT } from '~/constants';
|
import { DATE_FORMAT } from '~/constants';
|
||||||
import { User } from '~/types/app';
|
|
||||||
|
|
||||||
dayjs.extend(relativeTime);
|
dayjs.extend(relativeTime);
|
||||||
|
|
||||||
export type UserWithCounts = User & {
|
export type Columns = keyof UserWithCounters;
|
||||||
linksCount: number;
|
|
||||||
collectionsCount: number;
|
|
||||||
};
|
|
||||||
export type UsersWithCounts = UserWithCounts[];
|
|
||||||
|
|
||||||
export type Columns = keyof UserWithCounts;
|
|
||||||
|
|
||||||
const DEFAULT_SORT_BY: Columns = 'lastSeenAt';
|
const DEFAULT_SORT_BY: Columns = 'lastSeenAt';
|
||||||
const DEFAULT_SORT_DIRECTION = true;
|
const DEFAULT_SORT_DIRECTION = true;
|
||||||
|
|
||||||
export interface UsersTableProps {
|
export interface UsersTableProps {
|
||||||
users: UsersWithCounts;
|
users: UserWithCounters[];
|
||||||
totalCollections: number;
|
totalCollections: number;
|
||||||
totalLinks: 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;
|
const reversed = field === sortBy ? !reverseSortDirection : false;
|
||||||
setReverseSortDirection(reversed);
|
setReverseSortDirection(reversed);
|
||||||
setSortBy(field);
|
setSortBy(field);
|
||||||
@@ -75,11 +69,14 @@ export function UsersTable({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderDateCell = (date: string) => (
|
const renderDateCell = (date: string | null) => {
|
||||||
<Tooltip label={dayjs(date).format(DATE_FORMAT).toString()}>
|
if (!date) return '-';
|
||||||
<Text>{dayjs(date).fromNow()}</Text>
|
return (
|
||||||
</Tooltip>
|
<Tooltip label={dayjs(date).format(DATE_FORMAT).toString()}>
|
||||||
);
|
<Text>{dayjs(date).fromNow()}</Text>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const rows = sortedData.map((user) => (
|
const rows = sortedData.map((user) => (
|
||||||
<Table.Tr key={user.id}>
|
<Table.Tr key={user.id}>
|
||||||
|
|||||||
@@ -1,22 +1,19 @@
|
|||||||
import {
|
import { UserWithCounters } from '#shared/types/dto';
|
||||||
UsersWithCounts,
|
|
||||||
UserWithCounts,
|
|
||||||
} from '~/components/admin/users/users_table';
|
|
||||||
|
|
||||||
export function filterData(data: UsersWithCounts, search: string) {
|
export function filterData(data: UserWithCounters[], search: string) {
|
||||||
const query = search.toLowerCase().trim();
|
const query = search.toLowerCase().trim();
|
||||||
return data.filter((item) =>
|
return data.filter((item) =>
|
||||||
['email', 'name', 'nickName', 'fullname'].some((key) => {
|
['email', 'fullname'].some((key) => {
|
||||||
const value = item[key as keyof UserWithCounts];
|
const value = item[key as keyof UserWithCounters];
|
||||||
return typeof value === 'string' && value.toLowerCase().includes(query);
|
return typeof value === 'string' && value.toLowerCase().includes(query);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sortData(
|
export function sortData(
|
||||||
data: UsersWithCounts,
|
data: UserWithCounters[],
|
||||||
payload: {
|
payload: {
|
||||||
sortBy: keyof UserWithCounts | null;
|
sortBy: keyof UserWithCounters | null;
|
||||||
reversed: boolean;
|
reversed: boolean;
|
||||||
search: string;
|
search: string;
|
||||||
}
|
}
|
||||||
@@ -29,6 +26,7 @@ export function sortData(
|
|||||||
|
|
||||||
return filterData(
|
return filterData(
|
||||||
[...data].sort((a, b) => {
|
[...data].sort((a, b) => {
|
||||||
|
if (!a[sortBy] || !b[sortBy]) return 0;
|
||||||
if (payload.reversed) {
|
if (payload.reversed) {
|
||||||
return b[sortBy] > a[sortBy] ? 1 : -1;
|
return b[sortBy] > a[sortBy] ? 1 : -1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
import { UserWithCounters } from '#shared/types/dto';
|
||||||
import { Badge } from '@mantine/core';
|
import { Badge } from '@mantine/core';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import type { PublicUser, User } from '~/types/app';
|
|
||||||
|
|
||||||
interface UserBadgeRoleProps {
|
interface UserBadgeRoleProps {
|
||||||
user: User | PublicUser;
|
user: UserWithCounters;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function UserBadgeRole({ user }: UserBadgeRoleProps) {
|
export function UserBadgeRole({ user }: UserBadgeRoleProps) {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { CollectionWithLinks } from '#shared/types/dto';
|
||||||
import { Link } from '@inertiajs/react';
|
import { Link } from '@inertiajs/react';
|
||||||
import { route } from '@izzyjs/route/client';
|
import { route } from '@izzyjs/route/client';
|
||||||
import { Text } from '@mantine/core';
|
import { Text } from '@mantine/core';
|
||||||
@@ -5,7 +6,6 @@ import { useEffect, useRef } from 'react';
|
|||||||
import { AiFillFolderOpen, AiOutlineFolder } from 'react-icons/ai';
|
import { AiFillFolderOpen, AiOutlineFolder } from 'react-icons/ai';
|
||||||
import { useActiveCollection } from '~/hooks/collections/use_active_collection';
|
import { useActiveCollection } from '~/hooks/collections/use_active_collection';
|
||||||
import { appendCollectionId } from '~/lib/navigation';
|
import { appendCollectionId } from '~/lib/navigation';
|
||||||
import { CollectionWithLinks } from '~/types/app';
|
|
||||||
import classes from './collection_item.module.css';
|
import classes from './collection_item.module.css';
|
||||||
|
|
||||||
interface CollectionItemProps {
|
interface CollectionItemProps {
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
|
import { LinkWithCollection } from '#shared/types/dto';
|
||||||
import { Card, Flex, Group, Text } from '@mantine/core';
|
import { Card, Flex, Group, Text } from '@mantine/core';
|
||||||
import { ExternalLinkStyled } from '~/components/common/external_link_styled';
|
import { ExternalLinkStyled } from '~/components/common/external_link_styled';
|
||||||
import LinkFavicon from '~/components/dashboard/link/item/favicon/link_favicon';
|
import LinkFavicon from '~/components/dashboard/link/item/favicon/link_favicon';
|
||||||
import LinkControls from '~/components/dashboard/link/item/link_controls';
|
import LinkControls from '~/components/dashboard/link/item/link_controls';
|
||||||
import { LinkWithCollection } from '~/types/app';
|
|
||||||
import styles from './favorite_item.module.css';
|
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}>
|
<ExternalLinkStyled href={link.url} title={link.url}>
|
||||||
<Card className={styles.linkWrapper}>
|
<Card className={styles.linkWrapper}>
|
||||||
<Group gap="xs" wrap="nowrap">
|
<Group gap="xs" wrap="nowrap">
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Link } from '#shared/types/dto';
|
||||||
import { Link as InertiaLink, router } from '@inertiajs/react';
|
import { Link as InertiaLink, router } from '@inertiajs/react';
|
||||||
import { route } from '@izzyjs/route/client';
|
import { route } from '@izzyjs/route/client';
|
||||||
import { ActionIcon, Menu } from '@mantine/core';
|
import { ActionIcon, Menu } from '@mantine/core';
|
||||||
@@ -10,10 +11,9 @@ import { IoTrashOutline } from 'react-icons/io5';
|
|||||||
import { MdFavorite, MdFavoriteBorder } from 'react-icons/md';
|
import { MdFavorite, MdFavoriteBorder } from 'react-icons/md';
|
||||||
import { onFavorite } from '~/lib/favorite';
|
import { onFavorite } from '~/lib/favorite';
|
||||||
import { appendCollectionId, appendLinkId } from '~/lib/navigation';
|
import { appendCollectionId, appendLinkId } from '~/lib/navigation';
|
||||||
import { Link, PublicLink } from '~/types/app';
|
|
||||||
|
|
||||||
interface LinksControlsProps {
|
interface LinksControlsProps {
|
||||||
link: Link | PublicLink;
|
link: Link;
|
||||||
showGoToCollection?: boolean;
|
showGoToCollection?: boolean;
|
||||||
}
|
}
|
||||||
export default function LinkControls({
|
export default function LinkControls({
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
|
import { Link } from '#shared/types/dto';
|
||||||
import { Card, Flex, Group, Text } from '@mantine/core';
|
import { Card, Flex, Group, Text } from '@mantine/core';
|
||||||
import { AiFillStar } from 'react-icons/ai';
|
import { AiFillStar } from 'react-icons/ai';
|
||||||
import { ExternalLinkStyled } from '~/components/common/external_link_styled';
|
import { ExternalLinkStyled } from '~/components/common/external_link_styled';
|
||||||
import LinkFavicon from '~/components/dashboard/link/item/favicon/link_favicon';
|
import LinkFavicon from '~/components/dashboard/link/item/favicon/link_favicon';
|
||||||
import LinkControls from '~/components/dashboard/link/item/link_controls';
|
import LinkControls from '~/components/dashboard/link/item/link_controls';
|
||||||
import type { LinkListProps } from '~/components/dashboard/link/list/link_list';
|
import type { LinkListProps } from '~/components/dashboard/link/list/link_list';
|
||||||
import { Link, PublicLink } from '~/types/app';
|
|
||||||
import styles from './link.module.css';
|
import styles from './link.module.css';
|
||||||
|
|
||||||
interface LinkItemProps extends LinkListProps {
|
interface LinkItemProps extends LinkListProps {
|
||||||
link: Link | PublicLink;
|
link: Link;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LinkItem({ link, hideMenu: hideMenu = false }: LinkItemProps) {
|
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 { Checkbox, Select, TextInput } from '@mantine/core';
|
||||||
import { FormEvent } from 'react';
|
import { FormEvent } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import BackToDashboard from '~/components/common/navigation/back_to_dashboard';
|
import BackToDashboard from '~/components/common/navigation/back_to_dashboard';
|
||||||
import useSearchParam from '~/hooks/use_search_param';
|
import useSearchParam from '~/hooks/use_search_param';
|
||||||
import { FormLayout, FormLayoutProps } from '~/layouts/form_layout';
|
import { FormLayout, FormLayoutProps } from '~/layouts/form_layout';
|
||||||
import { Collection } from '~/types/app';
|
|
||||||
|
|
||||||
export type FormLinkData = {
|
export type FormLinkData = {
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
export const LS_LANG_KEY = 'language';
|
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)';
|
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 { PageProps } from '@adonisjs/inertia/types';
|
||||||
import { usePage } from '@inertiajs/react';
|
import { usePage } from '@inertiajs/react';
|
||||||
import { CollectionWithLinks } from '~/types/app';
|
|
||||||
|
|
||||||
interface UseActiveCollectionProps {
|
interface UseActiveCollectionProps {
|
||||||
activeCollection?: CollectionWithLinks;
|
activeCollection?: CollectionWithLinks;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { CollectionWithLinks } from '#shared/types/dto';
|
||||||
import { PageProps } from '@adonisjs/inertia/types';
|
import { PageProps } from '@adonisjs/inertia/types';
|
||||||
import { usePage } from '@inertiajs/react';
|
import { usePage } from '@inertiajs/react';
|
||||||
import { CollectionWithLinks } from '~/types/app';
|
|
||||||
|
|
||||||
interface UseCollectionsProps {
|
interface UseCollectionsProps {
|
||||||
collections: CollectionWithLinks[];
|
collections: CollectionWithLinks[];
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { LinkWithCollection } from '#shared/types/dto';
|
||||||
import { PageProps } from '@adonisjs/inertia/types';
|
import { PageProps } from '@adonisjs/inertia/types';
|
||||||
import { usePage } from '@inertiajs/react';
|
import { usePage } from '@inertiajs/react';
|
||||||
import { LinkWithCollection } from '~/types/app';
|
|
||||||
|
|
||||||
interface UseFavoriteLinksProps {
|
interface UseFavoriteLinksProps {
|
||||||
favoriteLinks: LinkWithCollection[];
|
favoriteLinks: LinkWithCollection[];
|
||||||
|
|||||||
@@ -1,25 +1,15 @@
|
|||||||
|
import { UserAuth } from '#shared/types/dto';
|
||||||
|
import { PageProps } from '@adonisjs/inertia/types';
|
||||||
import { usePage } from '@inertiajs/react';
|
import { usePage } from '@inertiajs/react';
|
||||||
import type { Auth, InertiaPage } from '~/types/inertia';
|
|
||||||
|
|
||||||
export const useAuth = () => usePage<InertiaPage>().props.auth;
|
const useAuth = (): UserAuth => usePage<PageProps>().props.auth as UserAuth;
|
||||||
|
const withAuth = <T extends object>(
|
||||||
export const withAuth = <T extends object>(
|
Component: React.ComponentType<T & { auth: UserAuth }>
|
||||||
Component: React.ComponentType<T & { auth: Auth }>
|
) => {
|
||||||
): React.ComponentType<Omit<T, 'auth'>> => {
|
return (props: T) => {
|
||||||
return (props: Omit<T, 'auth'>) => {
|
|
||||||
const auth = useAuth();
|
const auth = useAuth();
|
||||||
return <Component {...(props as T)} auth={auth} />;
|
return <Component {...props} auth={auth} />;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const withAuthRequired = <T extends object>(
|
export { useAuth, withAuth };
|
||||||
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} />;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { Link } from '#shared/types/dto';
|
||||||
import { route } from '@izzyjs/route/client';
|
import { route } from '@izzyjs/route/client';
|
||||||
import { makeRequest } from '~/lib/request';
|
import { makeRequest } from '~/lib/request';
|
||||||
import { Link } from '~/types/app';
|
|
||||||
|
|
||||||
export const onFavorite = (
|
export const onFavorite = (
|
||||||
linkId: Link['id'],
|
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 = (
|
export const appendCollectionId = (
|
||||||
url: string,
|
url: string,
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ import {
|
|||||||
} from '~/components/form/form_collection';
|
} from '~/components/form/form_collection';
|
||||||
import { Visibility } from '~/types/app';
|
import { Visibility } from '~/types/app';
|
||||||
|
|
||||||
|
interface CreateCollectionPageProps {
|
||||||
|
disableHomeLink: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export default function CreateCollectionPage({
|
export default function CreateCollectionPage({
|
||||||
disableHomeLink,
|
disableHomeLink,
|
||||||
}: {
|
}: CreateCollectionPageProps) {
|
||||||
disableHomeLink: boolean;
|
|
||||||
}) {
|
|
||||||
const { t } = useTranslation('common');
|
const { t } = useTranslation('common');
|
||||||
const { data, setData, submit, processing } = useForm<FormCollectionData>({
|
const { data, setData, submit, processing } = useForm<FormCollectionData>({
|
||||||
name: '',
|
name: '',
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Collection } from '#shared/types/dto';
|
||||||
import { useForm } from '@inertiajs/react';
|
import { useForm } from '@inertiajs/react';
|
||||||
import { route } from '@izzyjs/route/client';
|
import { route } from '@izzyjs/route/client';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -5,13 +6,14 @@ import {
|
|||||||
FormCollection,
|
FormCollection,
|
||||||
FormCollectionData,
|
FormCollectionData,
|
||||||
} from '~/components/form/form_collection';
|
} from '~/components/form/form_collection';
|
||||||
import { Collection } from '~/types/app';
|
|
||||||
|
interface DeleteCollectionPageProps {
|
||||||
|
collection: Collection;
|
||||||
|
}
|
||||||
|
|
||||||
export default function DeleteCollectionPage({
|
export default function DeleteCollectionPage({
|
||||||
collection,
|
collection,
|
||||||
}: {
|
}: DeleteCollectionPageProps) {
|
||||||
collection: Collection;
|
|
||||||
}) {
|
|
||||||
const { t } = useTranslation('common');
|
const { t } = useTranslation('common');
|
||||||
const { data, setData, submit, processing, errors } =
|
const { data, setData, submit, processing, errors } =
|
||||||
useForm<FormCollectionData>({
|
useForm<FormCollectionData>({
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Collection } from '#shared/types/dto';
|
||||||
import { useForm } from '@inertiajs/react';
|
import { useForm } from '@inertiajs/react';
|
||||||
import { route } from '@izzyjs/route/client';
|
import { route } from '@izzyjs/route/client';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
@@ -6,13 +7,14 @@ import {
|
|||||||
FormCollection,
|
FormCollection,
|
||||||
FormCollectionData,
|
FormCollectionData,
|
||||||
} from '~/components/form/form_collection';
|
} from '~/components/form/form_collection';
|
||||||
import { Collection } from '~/types/app';
|
|
||||||
|
interface EditCollectionPageProps {
|
||||||
|
collection: Collection;
|
||||||
|
}
|
||||||
|
|
||||||
export default function EditCollectionPage({
|
export default function EditCollectionPage({
|
||||||
collection,
|
collection,
|
||||||
}: {
|
}: EditCollectionPageProps) {
|
||||||
collection: Collection;
|
|
||||||
}) {
|
|
||||||
const { t } = useTranslation('common');
|
const { t } = useTranslation('common');
|
||||||
const { data, setData, submit, processing, errors } =
|
const { data, setData, submit, processing, errors } =
|
||||||
useForm<FormCollectionData>({
|
useForm<FormCollectionData>({
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Collection } from '#shared/types/dto';
|
||||||
import { useForm } from '@inertiajs/react';
|
import { useForm } from '@inertiajs/react';
|
||||||
import { route } from '@izzyjs/route/client';
|
import { route } from '@izzyjs/route/client';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
@@ -5,13 +6,12 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { FormLink } from '~/components/form/form_link';
|
import { FormLink } from '~/components/form/form_link';
|
||||||
import useSearchParam from '~/hooks/use_search_param';
|
import useSearchParam from '~/hooks/use_search_param';
|
||||||
import { isValidHttpUrl } from '~/lib/navigation';
|
import { isValidHttpUrl } from '~/lib/navigation';
|
||||||
import { Collection } from '~/types/app';
|
|
||||||
|
|
||||||
export default function CreateLinkPage({
|
interface CreateLinkPageProps {
|
||||||
collections,
|
|
||||||
}: {
|
|
||||||
collections: Collection[];
|
collections: Collection[];
|
||||||
}) {
|
}
|
||||||
|
|
||||||
|
export default function CreateLinkPage({ collections }: CreateLinkPageProps) {
|
||||||
const { t } = useTranslation('common');
|
const { t } = useTranslation('common');
|
||||||
const collectionId =
|
const collectionId =
|
||||||
Number(useSearchParam('collectionId')) ?? collections[0].id;
|
Number(useSearchParam('collectionId')) ?? collections[0].id;
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
|
import { LinkWithCollection } from '#shared/types/dto';
|
||||||
import { useForm } from '@inertiajs/react';
|
import { useForm } from '@inertiajs/react';
|
||||||
import { route } from '@izzyjs/route/client';
|
import { route } from '@izzyjs/route/client';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FormLink } from '~/components/form/form_link';
|
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 { t } = useTranslation('common');
|
||||||
const { data, setData, submit, processing } = useForm({
|
const { data, setData, submit, processing } = useForm({
|
||||||
name: link.name,
|
name: link.name,
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
|
import { Collection, LinkWithCollection } from '#shared/types/dto';
|
||||||
import { useForm } from '@inertiajs/react';
|
import { useForm } from '@inertiajs/react';
|
||||||
import { route } from '@izzyjs/route/client';
|
import { route } from '@izzyjs/route/client';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FormLink } from '~/components/form/form_link';
|
import { FormLink } from '~/components/form/form_link';
|
||||||
import { isValidHttpUrl } from '~/lib/navigation';
|
import { isValidHttpUrl } from '~/lib/navigation';
|
||||||
import { Collection, Link } from '~/types/app';
|
|
||||||
|
|
||||||
export default function EditLinkPage({
|
interface EditLinkPageProps {
|
||||||
collections,
|
|
||||||
link,
|
|
||||||
}: {
|
|
||||||
collections: Collection[];
|
collections: Collection[];
|
||||||
link: Link;
|
link: LinkWithCollection;
|
||||||
}) {
|
}
|
||||||
|
|
||||||
|
export default function EditLinkPage({ collections, link }: EditLinkPageProps) {
|
||||||
const { t } = useTranslation('common');
|
const { t } = useTranslation('common');
|
||||||
const { data, setData, submit, processing } = useForm({
|
const { data, setData, submit, processing } = useForm({
|
||||||
name: link.name,
|
name: link.name,
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
|
import { SharedCollection } from '#shared/types/dto';
|
||||||
import { Flex, Text } from '@mantine/core';
|
import { Flex, Text } from '@mantine/core';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { LinkList } from '~/components/dashboard/link/list/link_list';
|
import { LinkList } from '~/components/dashboard/link/list/link_list';
|
||||||
import type { CollectionWithLinks, PublicUser } from '~/types/app';
|
|
||||||
|
|
||||||
interface SharedPageProps {
|
interface SharedPageProps {
|
||||||
activeCollection: CollectionWithLinks & { author: PublicUser };
|
activeCollection: SharedCollection;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SharedPage({ activeCollection }: SharedPageProps) {
|
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 {
|
export enum Visibility {
|
||||||
PUBLIC = 'PUBLIC',
|
PUBLIC = 'PUBLIC',
|
||||||
PRIVATE = 'PRIVATE',
|
PRIVATE = 'PRIVATE',
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
"#adonis/api": "./.adonisjs/api.ts",
|
"#adonis/api": "./.adonisjs/api.ts",
|
||||||
"#config/*": "./config/*.js",
|
"#config/*": "./config/*.js",
|
||||||
"#controllers/*": "./app/controllers/*.js",
|
"#controllers/*": "./app/controllers/*.js",
|
||||||
|
"#dtos/*": "./app/dtos/*.js",
|
||||||
"#models/*": "./app/models/*.js",
|
"#models/*": "./app/models/*.js",
|
||||||
"#routes/*": "./app/routes/*.js",
|
"#routes/*": "./app/routes/*.js",
|
||||||
"#services/*": "./app/services/*.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