refactor: controllers and models to adapt them to the previous version of my-links

This commit is contained in:
Sonny
2024-05-24 23:54:43 +02:00
committed by Sonny
parent 31b4f22772
commit b28499a69a
19 changed files with 113 additions and 85 deletions

View File

@@ -120,6 +120,7 @@ export default class CollectionsController {
async getCollectionsByAuthorId(authorId: User['id']) { async getCollectionsByAuthorId(authorId: User['id']) {
return await Collection.query() return await Collection.query()
.where('author_id', authorId) .where('author_id', authorId)
.orderBy('created_at')
.preload('links'); .preload('links');
} }

View File

@@ -49,6 +49,7 @@ export default class UsersController {
nickName, nickName,
avatarUrl, avatarUrl,
token, token,
providerType: 'google',
} }
); );

View File

@@ -10,7 +10,7 @@ export default class LogRequest {
!request.url().startsWith('/@react-refresh') && !request.url().startsWith('/@react-refresh') &&
!request.url().includes('.ts') !request.url().includes('.ts')
) { ) {
logger.info(`-> ${request.method()}: ${request.url()}`); logger.info(`[${request.method()}]: ${request.url()}`);
} }
await next(); await next();
} }

View File

@@ -1,29 +1,27 @@
import { BaseModel, CamelCaseNamingStrategy, beforeCreate, column } from '@adonisjs/lucid/orm'; import {
BaseModel,
CamelCaseNamingStrategy,
column,
} from '@adonisjs/lucid/orm';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { v4 as uuidv4 } from 'uuid';
export default class AppBaseModel extends BaseModel { export default class AppBaseModel extends BaseModel {
static namingStrategy = new CamelCaseNamingStrategy(); static namingStrategy = new CamelCaseNamingStrategy();
static selfAssignPrimaryKey = true; static selfAssignPrimaryKey = true;
@column({ isPrimary: true }) @column({ isPrimary: true })
declare id: string; // UUID declare id: number;
@column.dateTime({ @column.dateTime({
autoCreate: true, autoCreate: true,
serializeAs: 'createdAt', serializeAs: 'created_at',
}) })
declare createdAt: DateTime; declare created_at: DateTime;
@column.dateTime({ @column.dateTime({
autoCreate: true, autoCreate: true,
autoUpdate: true, autoUpdate: true,
serializeAs: 'updatedAt', serializeAs: 'updated_at',
}) })
declare updatedAt: DateTime; declare updated_at: DateTime;
@beforeCreate()
static assignUuid(item: any) {
item.id = uuidv4();
}
} }

View File

@@ -16,10 +16,10 @@ export default class Collection extends AppBaseModel {
declare visibility: Visibility; declare visibility: Visibility;
@column() @column()
declare nextId: string; declare next_id: number;
@column() @column()
declare authorId: string; declare authorId: number;
@belongsTo(() => User, { foreignKey: 'authorId' }) @belongsTo(() => User, { foreignKey: 'authorId' })
declare author: BelongsTo<typeof User>; declare author: BelongsTo<typeof User>;

View File

@@ -9,7 +9,7 @@ export default class Link extends AppBaseModel {
declare name: string; declare name: string;
@column() @column()
declare description: string; declare description: string | null;
@column() @column()
declare url: string; declare url: string;
@@ -18,13 +18,13 @@ export default class Link extends AppBaseModel {
declare favorite: boolean; declare favorite: boolean;
@column() @column()
declare collectionId: string; declare collectionId: number;
@belongsTo(() => Collection, { foreignKey: 'collectionId' }) @belongsTo(() => Collection, { foreignKey: 'collectionId' })
declare collection: BelongsTo<typeof Collection>; declare collection: BelongsTo<typeof Collection>;
@column() @column()
declare authorId: string; declare authorId: number;
@belongsTo(() => User, { foreignKey: 'authorId' }) @belongsTo(() => User, { foreignKey: 'authorId' })
declare author: BelongsTo<typeof User>; declare author: BelongsTo<typeof User>;

View File

@@ -1,7 +1,7 @@
import Collection from '#models/collection'; import Collection from '#models/collection';
import Link from '#models/link'; import Link from '#models/link';
import type { GoogleToken } from '@adonisjs/ally/types'; import type { GoogleToken } from '@adonisjs/ally/types';
import { column, manyToMany } from '@adonisjs/lucid/orm'; import { column, computed, manyToMany } from '@adonisjs/lucid/orm';
import type { ManyToMany } from '@adonisjs/lucid/types/relations'; import type { ManyToMany } from '@adonisjs/lucid/types/relations';
import AppBaseModel from './app_base_model.js'; import AppBaseModel from './app_base_model.js';
@@ -12,20 +12,23 @@ export default class User extends AppBaseModel {
@column() @column()
declare name: string; declare name: string;
@column({ serializeAs: 'nickName' }) @column()
declare nickName: string; // public username declare nickName: string; // public username
@column({ serializeAs: 'avatarUrl' }) @column()
declare avatarUrl: string; declare avatarUrl: string;
@column() @column()
declare isAdmin: boolean; declare isAdmin: boolean;
@column({ serializeAs: null }) @column({ serializeAs: null })
declare token: GoogleToken; declare token?: GoogleToken;
@column({ serializeAs: null }) @column({ serializeAs: null })
declare providerId: string; declare providerId: number;
@column({ serializeAs: null })
declare providerType: 'google';
@manyToMany(() => Collection, { @manyToMany(() => Collection, {
relatedKey: 'authorId', relatedKey: 'authorId',
@@ -36,4 +39,9 @@ export default class User extends AppBaseModel {
relatedKey: 'authorId', relatedKey: 'authorId',
}) })
declare links: ManyToMany<typeof Link>; declare links: ManyToMany<typeof Link>;
@computed()
get fullname() {
return this.nickName || this.name;
}
} }

View File

@@ -2,7 +2,7 @@ import vine, { SimpleMessagesProvider } from '@vinejs/vine';
import { Visibility } from '../enums/visibility.js'; import { Visibility } from '../enums/visibility.js';
const params = vine.object({ const params = vine.object({
id: vine.string().trim(), id: vine.number(),
}); });
export const createCollectionValidator = vine.compile( export const createCollectionValidator = vine.compile(
@@ -10,7 +10,7 @@ export const createCollectionValidator = vine.compile(
name: vine.string().trim().minLength(1).maxLength(254), name: vine.string().trim().minLength(1).maxLength(254),
description: vine.string().trim().maxLength(254).nullable(), description: vine.string().trim().maxLength(254).nullable(),
visibility: vine.enum(Visibility), visibility: vine.enum(Visibility),
nextId: vine.string().optional(), nextId: vine.number(),
}) })
); );
@@ -19,7 +19,7 @@ export const updateCollectionValidator = vine.compile(
name: vine.string().trim().minLength(1).maxLength(254), name: vine.string().trim().minLength(1).maxLength(254),
description: vine.string().trim().maxLength(254).nullable(), description: vine.string().trim().maxLength(254).nullable(),
visibility: vine.enum(Visibility), visibility: vine.enum(Visibility),
nextId: vine.string().optional(), nextId: vine.number(),
params, params,
}) })

View File

@@ -1,7 +1,7 @@
import vine from '@vinejs/vine'; import vine from '@vinejs/vine';
const params = vine.object({ const params = vine.object({
id: vine.string().trim(), id: vine.number(),
}); });
export const createLinkValidator = vine.compile( export const createLinkValidator = vine.compile(
@@ -10,7 +10,7 @@ export const createLinkValidator = vine.compile(
description: vine.string().trim().maxLength(300).optional(), description: vine.string().trim().maxLength(300).optional(),
url: vine.string().trim(), url: vine.string().trim(),
favorite: vine.boolean(), favorite: vine.boolean(),
collectionId: vine.string().trim(), collectionId: vine.number(),
}) })
); );
@@ -20,7 +20,7 @@ export const updateLinkValidator = vine.compile(
description: vine.string().trim().maxLength(300).optional(), description: vine.string().trim().maxLength(300).optional(),
url: vine.string().trim(), url: vine.string().trim(),
favorite: vine.boolean(), favorite: vine.boolean(),
collectionId: vine.string().trim(), collectionId: vine.number(),
params, params,
}) })
@@ -37,7 +37,7 @@ export const updateLinkFavoriteStatusValidator = vine.compile(
favorite: vine.boolean(), favorite: vine.boolean(),
params: vine.object({ params: vine.object({
id: vine.string().trim(), id: vine.number(),
}), }),
}) })
); );

9
app/validators/search.ts Normal file
View File

@@ -0,0 +1,9 @@
import vine from '@vinejs/vine';
export const searchValidator = vine.compile(
vine.object({
searchTerm: vine.string().trim().minLength(1).maxLength(254),
links: vine.boolean(),
collections: vine.boolean(),
})
);

View File

@@ -0,0 +1,8 @@
import { Knex } from 'knex';
export function defaultTableFields(table: Knex.CreateTableBuilder) {
table.increments('id', { primaryKey: true }).first().unique().notNullable();
table.timestamp('created_at').notNullable();
table.timestamp('updated_at').nullable();
}

View File

@@ -1,27 +1,26 @@
import { defaultTableFields } from '#database/default_table_fields';
import { BaseSchema } from '@adonisjs/lucid/schema'; import { BaseSchema } from '@adonisjs/lucid/schema';
export default class extends BaseSchema { export default class CreateUsersTable extends BaseSchema {
protected tableName = 'users'; static tableName = 'users';
async up() { async up() {
this.schema.createTable(this.tableName, (table) => { this.schema.createTableIfNotExists(CreateUsersTable.tableName, (table) => {
table.uuid('id').primary().unique().notNullable();
table.string('email', 254).notNullable().unique(); table.string('email', 254).notNullable().unique();
table.string('name', 254).notNullable(); table.string('name', 254).notNullable();
table.string('nick_name', 254).notNullable(); table.string('nick_name', 254).nullable();
table.text('avatar_url').notNullable(); table.text('avatar_url').notNullable();
table.boolean('is_admin').defaultTo(0).notNullable(); table.boolean('is_admin').defaultTo(0).notNullable();
table.json('token').notNullable(); table.json('token').nullable();
table.string('provider_id').notNullable(); table.string('provider_id').notNullable();
table.enum('provider_type', ['google']).notNullable().defaultTo('google');
table.timestamp('created_at').notNullable(); defaultTableFields(table);
table.timestamp('updated_at').nullable();
}); });
} }
async down() { async down() {
this.schema.dropTable(this.tableName); this.schema.dropTable(CreateUsersTable.tableName);
} }
} }

View File

@@ -1,40 +1,44 @@
import { defaultTableFields } from '#database/default_table_fields';
import { BaseSchema } from '@adonisjs/lucid/schema'; import { BaseSchema } from '@adonisjs/lucid/schema';
import { Visibility } from '../../app/enums/visibility.js'; import { Visibility } from '../../app/enums/visibility.js';
export default class extends BaseSchema { export default class CreateCollectionTable extends BaseSchema {
protected tableName = 'collections'; static tableName = 'collections';
private visibilityEnumName = 'collection_visibility'; private visibilityEnumName = 'collection_visibility';
async up() { async up() {
this.schema.raw(`DROP TYPE IF EXISTS ${this.visibilityEnumName}`); this.schema.raw(`DROP TYPE IF EXISTS ${this.visibilityEnumName}`);
this.schema.createTable(this.tableName, (table) => { this.schema.createTableIfNotExists(
table.uuid('id').primary().unique().notNullable(); CreateCollectionTable.tableName,
(table) => {
table.string('name', 254).notNullable();
table.string('description', 254).nullable();
table
.enum('visibility', Object.values(Visibility), {
useNative: true,
enumName: this.visibilityEnumName,
existingType: false,
})
.nullable()
.defaultTo(Visibility.PRIVATE);
table
.integer('next_id')
.references('id')
.inTable('collections')
.defaultTo(null);
table
.integer('author_id')
.references('id')
.inTable('users')
.onDelete('CASCADE');
table.string('name', 254).notNullable(); defaultTableFields(table);
table.string('description', 254); }
table );
.uuid('next_id')
.references('id')
.inTable('collections')
.defaultTo(null);
table
.uuid('author_id')
.references('id')
.inTable('users')
.onDelete('CASCADE');
table.enum('visibility', Object.values(Visibility), {
useNative: true,
enumName: this.visibilityEnumName,
existingType: false,
});
table.timestamp('created_at');
table.timestamp('updated_at');
});
} }
async down() { async down() {
this.schema.raw(`DROP TYPE IF EXISTS ${this.visibilityEnumName}`); this.schema.raw(`DROP TYPE IF EXISTS ${this.visibilityEnumName}`);
this.schema.dropTable(this.tableName); this.schema.dropTable(CreateCollectionTable.tableName);
} }
} }

View File

@@ -1,33 +1,31 @@
import { defaultTableFields } from '#database/default_table_fields';
import { BaseSchema } from '@adonisjs/lucid/schema'; import { BaseSchema } from '@adonisjs/lucid/schema';
export default class extends BaseSchema { export default class CreateLinksTable extends BaseSchema {
protected tableName = 'links'; static tableName = 'links';
async up() { async up() {
this.schema.createTable(this.tableName, (table) => { this.schema.createTableIfNotExists(CreateLinksTable.tableName, (table) => {
table.uuid('id').primary().unique().notNullable();
table.string('name', 254).notNullable(); table.string('name', 254).notNullable();
table.string('description', 254); table.string('description', 254).nullable();
table.text('url').notNullable(); table.text('url').notNullable();
table.boolean('favorite').notNullable().defaultTo(0); table.boolean('favorite').notNullable().defaultTo(0);
table table
.uuid('collection_id') .integer('collection_id')
.references('id') .references('id')
.inTable('collections') .inTable('collections')
.onDelete('CASCADE'); .onDelete('CASCADE');
table table
.uuid('author_id') .integer('author_id')
.references('id') .references('id')
.inTable('users') .inTable('users')
.onDelete('CASCADE'); .onDelete('CASCADE');
table.timestamp('created_at'); defaultTableFields(table);
table.timestamp('updated_at');
}); });
} }
async down() { async down() {
this.schema.dropTable(this.tableName); this.schema.dropTable(CreateLinksTable.tableName);
} }
} }

View File

@@ -16,13 +16,14 @@ export default class extends BaseSeeder {
export function createRandomUser() { export function createRandomUser() {
return { return {
id: faker.string.uuid(), id: faker.number.int(),
email: faker.internet.email(), email: faker.internet.email(),
name: faker.internet.userName(), name: faker.internet.userName(),
nickName: faker.internet.displayName(), nickName: faker.internet.displayName(),
avatarUrl: faker.image.avatar(), avatarUrl: faker.image.avatar(),
isAdmin: false, isAdmin: false,
providerId: faker.string.uuid(), providerId: faker.number.int(),
providerType: 'google' as const,
token: {} as GoogleToken, token: {} as GoogleToken,
}; };
} }

View File

@@ -4,7 +4,7 @@ import useUser from '~/hooks/use_user';
export default function UserCard() { export default function UserCard() {
const { user, isAuthenticated } = useUser(); const { user, isAuthenticated } = useUser();
const altImage = `${user?.nickName}'s avatar`; const altImage = `${user?.fullname}'s avatar`;
return ( return (
isAuthenticated && ( isAuthenticated && (
<Item className="disable-hover"> <Item className="disable-hover">
@@ -14,7 +14,7 @@ export default function UserCard() {
alt={altImage} alt={altImage}
referrerPolicy="no-referrer" referrerPolicy="no-referrer"
/> />
{user.nickName} {user.fullname}
</Item> </Item>
) )
); );

View File

@@ -103,7 +103,7 @@ function ProfileDropdown() {
width={22} width={22}
referrerPolicy="no-referrer" referrerPolicy="no-referrer"
/> />
{user!.nickName} {user!.fullname}
</UserCard> </UserCard>
} }
> >

View File

@@ -1,7 +1,7 @@
import { Link } from '@inertiajs/react'; import { Link } 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 { BsGear } from 'react-icons/bs'; import { PiGearLight } from 'react-icons/pi';
import Modal from '~/components/common/modal/modal'; import Modal from '~/components/common/modal/modal';
import LangSelector from '~/components/lang_selector'; import LangSelector from '~/components/lang_selector';
import ThemeSwitcher from '~/components/theme_switcher'; import ThemeSwitcher from '~/components/theme_switcher';
@@ -20,7 +20,7 @@ export default function ModalSettings({
return ( return (
<> <>
<OpenItem onClick={open}> <OpenItem onClick={open}>
<BsGear /> <PiGearLight />
{t('settings')} {t('settings')}
</OpenItem> </OpenItem>
<Modal title={t('settings')} opened={isShowing} close={close}> <Modal title={t('settings')} opened={isShowing} close={close}>

View File

@@ -1,10 +1,11 @@
import type Collection from '#models/collection'; import type Collection from '#models/collection';
type User = { type User = {
id: string; id: number;
email: string; email: string;
name: string; name: string;
nickName: string; nickName: string;
fullname: string;
avatarUrl: string; avatarUrl: string;
isAdmin: boolean; isAdmin: boolean;
collections: Collection[]; collections: Collection[];