mirror of
https://github.com/Sonny93/my-links.git
synced 2025-12-08 22:53:25 +00:00
refactor: split backend by context instead of type 'controllers/models/...'
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import CollectionsController from '#controllers/collections_controller';
|
||||
import LinksController from '#controllers/links_controller';
|
||||
import UsersController from '#controllers/users_controller';
|
||||
import User from '#models/user';
|
||||
import AuthController from '#auth/controllers/auth_controller';
|
||||
import CollectionsController from '#collections/controllers/collections_controller';
|
||||
import LinksController from '#links/controllers/links_controller';
|
||||
import User from '#user/models/user';
|
||||
import { inject } from '@adonisjs/core';
|
||||
import type { HttpContext } from '@adonisjs/core/http';
|
||||
import { HttpContext } from '@adonisjs/core/http';
|
||||
|
||||
class UserWithRelationCountDto {
|
||||
constructor(private user: User) {}
|
||||
@@ -26,7 +26,7 @@ class UserWithRelationCountDto {
|
||||
@inject()
|
||||
export default class AdminController {
|
||||
constructor(
|
||||
protected usersController: UsersController,
|
||||
protected usersController: AuthController,
|
||||
protected linksController: LinksController,
|
||||
protected collectionsController: CollectionsController
|
||||
) {}
|
||||
14
app/admin/routes/admin_routes.ts
Normal file
14
app/admin/routes/admin_routes.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { middleware } from '#start/kernel';
|
||||
import router from '@adonisjs/core/services/router';
|
||||
|
||||
const AdminController = () => import('#admin/controllers/admin_controller');
|
||||
|
||||
/**
|
||||
* Routes for admin dashboard
|
||||
*/
|
||||
router
|
||||
.group(() => {
|
||||
router.get('/', [AdminController, 'index']).as('admin.dashboard');
|
||||
})
|
||||
.middleware([middleware.auth(), middleware.admin()])
|
||||
.prefix('/admin');
|
||||
1
app/admin/routes/routes.ts
Normal file
1
app/admin/routes/routes.ts
Normal file
@@ -0,0 +1 @@
|
||||
import './admin_routes.js';
|
||||
@@ -1,10 +1,10 @@
|
||||
import User from '#models/user';
|
||||
import User from '#user/models/user';
|
||||
import type { HttpContext } from '@adonisjs/core/http';
|
||||
import logger from '@adonisjs/core/services/logger';
|
||||
import db from '@adonisjs/lucid/services/db';
|
||||
import { RouteName } from '@izzyjs/route/types';
|
||||
|
||||
export default class UsersController {
|
||||
export default class AuthController {
|
||||
private redirectTo: RouteName = 'auth';
|
||||
|
||||
login({ inertia }: HttpContext) {
|
||||
27
app/auth/routes/auth_routes.ts
Normal file
27
app/auth/routes/auth_routes.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { middleware } from '#start/kernel';
|
||||
import router from '@adonisjs/core/services/router';
|
||||
const AuthController = () => import('#auth/controllers/auth_controller');
|
||||
|
||||
const ROUTES_PREFIX = '/auth';
|
||||
|
||||
/**
|
||||
* Auth routes for unauthicated users
|
||||
*/
|
||||
router
|
||||
.group(() => {
|
||||
router.get('/google', [AuthController, 'google']).as('auth');
|
||||
router
|
||||
.get('/callback', [AuthController, 'callbackAuth'])
|
||||
.as('auth.callback');
|
||||
})
|
||||
.prefix(ROUTES_PREFIX);
|
||||
|
||||
/**
|
||||
* Routes for authenticated users
|
||||
*/
|
||||
router
|
||||
.group(() => {
|
||||
router.get('/logout', [AuthController, 'logout']).as('auth.logout');
|
||||
})
|
||||
.middleware([middleware.auth()])
|
||||
.prefix(ROUTES_PREFIX);
|
||||
1
app/auth/routes/routes.ts
Normal file
1
app/auth/routes/routes.ts
Normal file
@@ -0,0 +1 @@
|
||||
import './auth_routes.js';
|
||||
@@ -1,10 +1,8 @@
|
||||
import Collection from '#models/collection';
|
||||
import User from '#models/user';
|
||||
import {
|
||||
createCollectionValidator,
|
||||
deleteCollectionValidator,
|
||||
updateCollectionValidator,
|
||||
} from '#validators/collection';
|
||||
import Collection from '#collections/models/collection';
|
||||
import { createCollectionValidator } from '#collections/validators/create_collection_validator';
|
||||
import { deleteCollectionValidator } from '#collections/validators/delete_collection_validator';
|
||||
import { updateCollectionValidator } from '#collections/validators/update_collection_validator';
|
||||
import User from '#user/models/user';
|
||||
import type { HttpContext } from '@adonisjs/core/http';
|
||||
import db from '@adonisjs/lucid/services/db';
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import AppBaseModel from '#models/app_base_model';
|
||||
import Link from '#models/link';
|
||||
import User from '#models/user';
|
||||
import { Visibility } from '#collections/enums/visibility';
|
||||
import AppBaseModel from '#core/models/app_base_model';
|
||||
import Link from '#links/models/link';
|
||||
import User from '#user/models/user';
|
||||
import { belongsTo, column, hasMany } from '@adonisjs/lucid/orm';
|
||||
import type { BelongsTo, HasMany } from '@adonisjs/lucid/types/relations';
|
||||
import { Visibility } from '#enums/visibility';
|
||||
|
||||
export default class Collection extends AppBaseModel {
|
||||
@column()
|
||||
35
app/collections/routes/collections_routes.ts
Normal file
35
app/collections/routes/collections_routes.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { middleware } from '#start/kernel';
|
||||
import router from '@adonisjs/core/services/router';
|
||||
const CollectionsController = () =>
|
||||
import('#collections/controllers/collections_controller');
|
||||
|
||||
router
|
||||
.group(() => {
|
||||
router.get('/dashboard', [CollectionsController, 'index']).as('dashboard');
|
||||
|
||||
router
|
||||
.group(() => {
|
||||
router
|
||||
.get('/create', [CollectionsController, 'showCreatePage'])
|
||||
.as('collection.create-form');
|
||||
router
|
||||
.post('/', [CollectionsController, 'store'])
|
||||
.as('collection.create');
|
||||
|
||||
router
|
||||
.get('/edit', [CollectionsController, 'showEditPage'])
|
||||
.as('collection.edit-form');
|
||||
router
|
||||
.put('/:id', [CollectionsController, 'update'])
|
||||
.as('collection.edit');
|
||||
|
||||
router
|
||||
.get('/delete', [CollectionsController, 'showDeletePage'])
|
||||
.as('collection.delete-form');
|
||||
router
|
||||
.delete('/:id', [CollectionsController, 'delete'])
|
||||
.as('collection.delete');
|
||||
})
|
||||
.prefix('/collections');
|
||||
})
|
||||
.middleware([middleware.auth()]);
|
||||
1
app/collections/routes/routes.ts
Normal file
1
app/collections/routes/routes.ts
Normal file
@@ -0,0 +1 @@
|
||||
import './collections_routes.js';
|
||||
@@ -1,10 +1,6 @@
|
||||
import { Visibility } from '#enums/visibility';
|
||||
import { Visibility } from '#collections/enums/visibility';
|
||||
import vine, { SimpleMessagesProvider } from '@vinejs/vine';
|
||||
|
||||
const params = vine.object({
|
||||
id: vine.number(),
|
||||
});
|
||||
|
||||
export const createCollectionValidator = vine.compile(
|
||||
vine.object({
|
||||
name: vine.string().trim().minLength(1).maxLength(254),
|
||||
@@ -14,23 +10,6 @@ export const createCollectionValidator = vine.compile(
|
||||
})
|
||||
);
|
||||
|
||||
export const updateCollectionValidator = vine.compile(
|
||||
vine.object({
|
||||
name: vine.string().trim().minLength(1).maxLength(254),
|
||||
description: vine.string().trim().maxLength(254).nullable(),
|
||||
visibility: vine.enum(Visibility),
|
||||
nextId: vine.number().optional(),
|
||||
|
||||
params,
|
||||
})
|
||||
);
|
||||
|
||||
export const deleteCollectionValidator = vine.compile(
|
||||
vine.object({
|
||||
params,
|
||||
})
|
||||
);
|
||||
|
||||
createCollectionValidator.messagesProvider = new SimpleMessagesProvider({
|
||||
name: 'Collection name is required',
|
||||
'visibility.required': 'Collection visibiliy is required',
|
||||
@@ -0,0 +1,8 @@
|
||||
import { params } from '#core/validators/params_object';
|
||||
import vine from '@vinejs/vine';
|
||||
|
||||
export const deleteCollectionValidator = vine.compile(
|
||||
vine.object({
|
||||
params,
|
||||
})
|
||||
);
|
||||
14
app/collections/validators/update_collection_validator.ts
Normal file
14
app/collections/validators/update_collection_validator.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Visibility } from '#collections/enums/visibility';
|
||||
import { params } from '#core/validators/params_object';
|
||||
import vine from '@vinejs/vine';
|
||||
|
||||
export const updateCollectionValidator = vine.compile(
|
||||
vine.object({
|
||||
name: vine.string().trim().minLength(1).maxLength(254),
|
||||
description: vine.string().trim().maxLength(254).nullable(),
|
||||
visibility: vine.enum(Visibility),
|
||||
nextId: vine.number().optional(),
|
||||
|
||||
params,
|
||||
})
|
||||
);
|
||||
@@ -1,13 +0,0 @@
|
||||
import { PREFER_DARK_THEME } from '#constants/session';
|
||||
import { updateUserThemeValidator } from '#validators/user';
|
||||
import type { HttpContext } from '@adonisjs/core/http';
|
||||
|
||||
export default class AppsController {
|
||||
async updateUserTheme({ request, session, response }: HttpContext) {
|
||||
const { preferDarkTheme } = await request.validateUsing(
|
||||
updateUserThemeValidator
|
||||
);
|
||||
session.put(PREFER_DARK_THEME, preferDarkTheme);
|
||||
return response.ok({ message: 'ok' });
|
||||
}
|
||||
}
|
||||
5
app/core/validators/params_object.ts
Normal file
5
app/core/validators/params_object.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import vine from '@vinejs/vine';
|
||||
|
||||
export const params = vine.object({
|
||||
id: vine.number(),
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import FaviconNotFoundException from '#exceptions/favicon_not_found_exception';
|
||||
import { cache } from '#lib/cache';
|
||||
import { cache } from '#core/lib/cache';
|
||||
import FaviconNotFoundException from '#favicons/exceptions/favicon_not_found_exception';
|
||||
import type { HttpContext } from '@adonisjs/core/http';
|
||||
import logger from '@adonisjs/core/services/logger';
|
||||
import { parse } from 'node-html-parser';
|
||||
10
app/favicons/routes/favicons_routes.ts
Normal file
10
app/favicons/routes/favicons_routes.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import router from '@adonisjs/core/services/router';
|
||||
const FaviconsController = () =>
|
||||
import('#favicons/controllers/favicons_controller');
|
||||
|
||||
/**
|
||||
* Favicon routes
|
||||
*/
|
||||
router.group(() => {
|
||||
router.get('/favicon', [FaviconsController, 'index']).as('favicon');
|
||||
});
|
||||
1
app/favicons/routes/routes.ts
Normal file
1
app/favicons/routes/routes.ts
Normal file
@@ -0,0 +1 @@
|
||||
import './favicons_routes.js';
|
||||
3
app/home/routes/home_routes.ts
Normal file
3
app/home/routes/home_routes.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import router from '@adonisjs/core/services/router';
|
||||
|
||||
router.on('/').renderInertia('home').as('home');
|
||||
1
app/home/routes/routes.ts
Normal file
1
app/home/routes/routes.ts
Normal file
@@ -0,0 +1 @@
|
||||
import './home_routes.js';
|
||||
4
app/legal/routes/legal_routes.ts
Normal file
4
app/legal/routes/legal_routes.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import router from '@adonisjs/core/services/router';
|
||||
|
||||
router.on('/terms').renderInertia('terms').as('terms');
|
||||
router.on('/privacy').renderInertia('privacy').as('privacy');
|
||||
1
app/legal/routes/routes.ts
Normal file
1
app/legal/routes/routes.ts
Normal file
@@ -0,0 +1 @@
|
||||
import './legal_routes.js';
|
||||
@@ -1,13 +1,11 @@
|
||||
import CollectionsController from '#controllers/collections_controller';
|
||||
import Link from '#models/link';
|
||||
import {
|
||||
createLinkValidator,
|
||||
deleteLinkValidator,
|
||||
updateLinkFavoriteStatusValidator,
|
||||
updateLinkValidator,
|
||||
} from '#validators/link';
|
||||
import CollectionsController from '#collections/controllers/collections_controller';
|
||||
import Link from '#links/models/link';
|
||||
import { createLinkValidator } from '#links/validators/create_link_validator';
|
||||
import { deleteLinkValidator } from '#links/validators/delete_link_validator';
|
||||
import { updateLinkFavoriteStatusValidator } from '#links/validators/update_favorite_link_validator';
|
||||
import { updateLinkValidator } from '#links/validators/update_link_validator';
|
||||
import { inject } from '@adonisjs/core';
|
||||
import type { HttpContext } from '@adonisjs/core/http';
|
||||
import { HttpContext } from '@adonisjs/core/http';
|
||||
import db from '@adonisjs/lucid/services/db';
|
||||
|
||||
@inject()
|
||||
@@ -1,6 +1,6 @@
|
||||
import AppBaseModel from '#models/app_base_model';
|
||||
import Collection from '#models/collection';
|
||||
import User from '#models/user';
|
||||
import Collection from '#collections/models/collection';
|
||||
import AppBaseModel from '#core/models/app_base_model';
|
||||
import User from '#user/models/user';
|
||||
import { belongsTo, column } from '@adonisjs/lucid/orm';
|
||||
import type { BelongsTo } from '@adonisjs/lucid/types/relations';
|
||||
|
||||
28
app/links/routes/links_routes.ts
Normal file
28
app/links/routes/links_routes.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { middleware } from '#start/kernel';
|
||||
import router from '@adonisjs/core/services/router';
|
||||
const LinksController = () => import('#links/controllers/links_controller');
|
||||
|
||||
/**
|
||||
* Routes for authenticated users
|
||||
*/
|
||||
router
|
||||
.group(() => {
|
||||
router
|
||||
.get('/create', [LinksController, 'showCreatePage'])
|
||||
.as('link.create-form');
|
||||
router.post('/', [LinksController, 'store']).as('link.create');
|
||||
|
||||
router.get('/edit', [LinksController, 'showEditPage']).as('link.edit-form');
|
||||
router.put('/:id', [LinksController, 'update']).as('link.edit');
|
||||
|
||||
router
|
||||
.put('/:id/favorite', [LinksController, 'toggleFavorite'])
|
||||
.as('link.toggle-favorite');
|
||||
|
||||
router
|
||||
.get('/delete', [LinksController, 'showDeletePage'])
|
||||
.as('link.delete-form');
|
||||
router.delete('/:id', [LinksController, 'delete']).as('link.delete');
|
||||
})
|
||||
.middleware([middleware.auth()])
|
||||
.prefix('/links');
|
||||
1
app/links/routes/routes.ts
Normal file
1
app/links/routes/routes.ts
Normal file
@@ -0,0 +1 @@
|
||||
import './links_routes.js';
|
||||
11
app/links/validators/create_link_validator.ts
Normal file
11
app/links/validators/create_link_validator.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import vine from '@vinejs/vine';
|
||||
|
||||
export const createLinkValidator = vine.compile(
|
||||
vine.object({
|
||||
name: vine.string().trim().minLength(1).maxLength(254),
|
||||
description: vine.string().trim().maxLength(300).optional(),
|
||||
url: vine.string().trim(),
|
||||
favorite: vine.boolean(),
|
||||
collectionId: vine.number(),
|
||||
})
|
||||
);
|
||||
8
app/links/validators/delete_link_validator.ts
Normal file
8
app/links/validators/delete_link_validator.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { params } from '#core/validators/params_object';
|
||||
import vine from '@vinejs/vine';
|
||||
|
||||
export const deleteLinkValidator = vine.compile(
|
||||
vine.object({
|
||||
params,
|
||||
})
|
||||
);
|
||||
10
app/links/validators/update_favorite_link_validator.ts
Normal file
10
app/links/validators/update_favorite_link_validator.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { params } from '#core/validators/params_object';
|
||||
import vine from '@vinejs/vine';
|
||||
|
||||
export const updateLinkFavoriteStatusValidator = vine.compile(
|
||||
vine.object({
|
||||
favorite: vine.boolean(),
|
||||
|
||||
params,
|
||||
})
|
||||
);
|
||||
14
app/links/validators/update_link_validator.ts
Normal file
14
app/links/validators/update_link_validator.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { params } from '#core/validators/params_object';
|
||||
import vine from '@vinejs/vine';
|
||||
|
||||
export const updateLinkValidator = vine.compile(
|
||||
vine.object({
|
||||
name: vine.string().trim().minLength(1).maxLength(254),
|
||||
description: vine.string().trim().maxLength(300).optional(),
|
||||
url: vine.string().trim(),
|
||||
favorite: vine.boolean(),
|
||||
collectionId: vine.number(),
|
||||
|
||||
params,
|
||||
})
|
||||
);
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { HttpContext } from '@adonisjs/core/http';
|
||||
import db from '@adonisjs/lucid/services/db';
|
||||
|
||||
export default class SearchesController {
|
||||
export default class SearchController {
|
||||
async search({ request, auth }: HttpContext) {
|
||||
const term = request.qs()?.term;
|
||||
if (!term) {
|
||||
1
app/search/routes/routes.ts
Normal file
1
app/search/routes/routes.ts
Normal file
@@ -0,0 +1 @@
|
||||
import './search_routes.js';
|
||||
14
app/search/routes/search_routes.ts
Normal file
14
app/search/routes/search_routes.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { middleware } from '#start/kernel';
|
||||
import router from '@adonisjs/core/services/router';
|
||||
|
||||
const SearchesController = () =>
|
||||
import('#search/controllers/search_controller');
|
||||
|
||||
/**
|
||||
* Search routes
|
||||
*/
|
||||
router
|
||||
.group(() => {
|
||||
router.get('/search', [SearchesController, 'search']).as('search');
|
||||
})
|
||||
.middleware([middleware.auth()]);
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Visibility } from '#enums/visibility';
|
||||
import Collection from '#models/collection';
|
||||
import Link from '#models/link';
|
||||
import User from '#models/user';
|
||||
import { getSharedCollectionValidator } from '#validators/shared_collection';
|
||||
import { Visibility } from '#collections/enums/visibility';
|
||||
import Collection from '#collections/models/collection';
|
||||
import Link from '#links/models/link';
|
||||
import User from '#user/models/user';
|
||||
import type { HttpContext } from '@adonisjs/core/http';
|
||||
import { getSharedCollectionValidator } from '../validators/shared_collection.js';
|
||||
|
||||
class LinkWithoutFavoriteDto {
|
||||
constructor(private link: Link) {}
|
||||
1
app/shared_collections/routes/routes.ts
Normal file
1
app/shared_collections/routes/routes.ts
Normal file
@@ -0,0 +1 @@
|
||||
import './shared_collections_routes.js';
|
||||
@@ -0,0 +1,6 @@
|
||||
import router from '@adonisjs/core/services/router';
|
||||
|
||||
const SharedCollectionsController = () =>
|
||||
import('#shared_collections/controllers/shared_collections_controller');
|
||||
|
||||
router.get('/shared/:id', [SharedCollectionsController, 'index']).as('shared');
|
||||
@@ -1,9 +1,6 @@
|
||||
import { params } from '#core/validators/params_object';
|
||||
import vine from '@vinejs/vine';
|
||||
|
||||
const params = vine.object({
|
||||
id: vine.number(),
|
||||
});
|
||||
|
||||
export const getSharedCollectionValidator = vine.compile(
|
||||
vine.object({
|
||||
params,
|
||||
12
app/user/controllers/theme_controller.ts
Normal file
12
app/user/controllers/theme_controller.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { PREFER_DARK_THEME } from '#user/constants/theme';
|
||||
import { updateThemeValidator } from '#user/validators/update_theme_validator';
|
||||
import type { HttpContext } from '@adonisjs/core/http';
|
||||
|
||||
export default class ThemeController {
|
||||
async index({ request, session, response }: HttpContext) {
|
||||
const { preferDarkTheme } =
|
||||
await request.validateUsing(updateThemeValidator);
|
||||
session.put(PREFER_DARK_THEME, preferDarkTheme);
|
||||
return response.ok({ message: 'ok' });
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import Collection from '#models/collection';
|
||||
import Link from '#models/link';
|
||||
import Collection from '#collections/models/collection';
|
||||
import AppBaseModel from '#core/models/app_base_model';
|
||||
import Link from '#links/models/link';
|
||||
import type { GoogleToken } from '@adonisjs/ally/types';
|
||||
import { column, computed, hasMany } from '@adonisjs/lucid/orm';
|
||||
import type { HasMany } from '@adonisjs/lucid/types/relations';
|
||||
import AppBaseModel from './app_base_model.js';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
export default class User extends AppBaseModel {
|
||||
1
app/user/routes/routes.ts
Normal file
1
app/user/routes/routes.ts
Normal file
@@ -0,0 +1 @@
|
||||
import './user_theme_route.js';
|
||||
5
app/user/routes/user_theme_route.ts
Normal file
5
app/user/routes/user_theme_route.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import router from '@adonisjs/core/services/router';
|
||||
|
||||
const ThemeController = () => import('#user/controllers/theme_controller');
|
||||
|
||||
router.post('/user/theme', [ThemeController, 'index']).as('user.theme');
|
||||
@@ -1,6 +1,6 @@
|
||||
import vine from '@vinejs/vine';
|
||||
|
||||
export const updateUserThemeValidator = vine.compile(
|
||||
export const updateThemeValidator = vine.compile(
|
||||
vine.object({
|
||||
preferDarkTheme: vine.boolean(),
|
||||
})
|
||||
@@ -1,43 +0,0 @@
|
||||
import vine from '@vinejs/vine';
|
||||
|
||||
const params = vine.object({
|
||||
id: vine.number(),
|
||||
});
|
||||
|
||||
export const createLinkValidator = vine.compile(
|
||||
vine.object({
|
||||
name: vine.string().trim().minLength(1).maxLength(254),
|
||||
description: vine.string().trim().maxLength(300).optional(),
|
||||
url: vine.string().trim(),
|
||||
favorite: vine.boolean(),
|
||||
collectionId: vine.number(),
|
||||
})
|
||||
);
|
||||
|
||||
export const updateLinkValidator = vine.compile(
|
||||
vine.object({
|
||||
name: vine.string().trim().minLength(1).maxLength(254),
|
||||
description: vine.string().trim().maxLength(300).optional(),
|
||||
url: vine.string().trim(),
|
||||
favorite: vine.boolean(),
|
||||
collectionId: vine.number(),
|
||||
|
||||
params,
|
||||
})
|
||||
);
|
||||
|
||||
export const deleteLinkValidator = vine.compile(
|
||||
vine.object({
|
||||
params,
|
||||
})
|
||||
);
|
||||
|
||||
export const updateLinkFavoriteStatusValidator = vine.compile(
|
||||
vine.object({
|
||||
favorite: vine.boolean(),
|
||||
|
||||
params: vine.object({
|
||||
id: vine.number(),
|
||||
}),
|
||||
})
|
||||
);
|
||||
Reference in New Issue
Block a user