我们正在寻找一种解决方案,该解决方案弥合了BAAS平台(例如AppWrite或Supabase)之间的差距,并从头开始构建。我们需要一个系统,该系统可以灵活地实施BI,数据管理,物联网,自动化和类似应用程序,同时还结合了身份验证,RBAC,社交登录,配备CRUD操作,测试等的前端等功能。 P>
这就是完善和嵌套样板的诞生方式。
来源:
精炼样板:https://github.com/poliath/poliath-refine-boilerplate
Nest.js样板:https://github.com/poliath/nestjs-poliath-boilerplate
快速启动指南:https://github.com/poliath/nestjs-poliath-boilerplate/blob/master/QUICK_START_GUIDE.md
这是快速入门指南
精炼 +巢。JS样板
上次编辑:Ivan Golubic
上次编辑时间:2023年8月16日下午3:27
本文档描述了使用Poliath Refine和Nest.js模板创建新项目的过程。
要求
node.js:最低版本为16,但强烈建议使用18版
npm:安装和更新
Docker:安装,配置和运行
IDE:建议使用WebStorm,尽管VS代码将成为
后端
为了启动我们的nest.js后端,我们必须基于模板创建我们的新项目
git clone --depth 1 https://github.com/poliath/nestjs-poliath-boilerplate.git my-app
cd my-app/
cp env-example .env
快速运行
为了启动项目而无需开发(用于快速测试),从您的应用程序目录运行命令:
docker compose up -d
此命令将:
- 等待Postgres数据库容器旋转
- 运行迁移(在数据库中创建所需的表)
- 运行播种机(创建2个用户和少数随机文章)
- 在生产模式下运行应用
前端
为了查看您的后端服务数据,我们必须运行我们的前端应用程序(基于完善的React应用程序)。克隆poliath-refine-boilerplate
存储库中的首选目录:
git clone --depth 1 https://github.com/poliath/poliath-refine-boilerplate.git frontend
现在,前往您的目录并安装依赖项:
cd frontend/
npm install
安装依赖项后,我们必须构建我们的项目并运行预览模式(基本上是一个可以为我们的静态内容服务的本地服务器。
npm run build
npm run preview
此命令将向您显示您的应用程序正在运行的位置,例如:
Local: http://localhost:4173/
前往位置并登录。您应该查看演示用户和文章。
RBAC解释了
后端和前端都已经实施了RBAC。管理员用户可以对用户对象进行所有操作,而用户用户只能查看文章并管理文章。在本文档的后面
- admin@example.com - Admin
- john.doe@example.com - User
**************************************************************************************************************************************************** ****** 两个用户的密码是: ********************** ***************************秘密
推断者解释了
refine.dev的重要功能之一是推论器(简而言之,它基于数据库中的数据生成CRUD仪表板,并为您提供代码)。如果您单击*************** 文章 ************************会看到弹出窗口。此弹出窗口在******* 用户 *******上都不可见样板虽然在文章中不是为了展示目的。您可以在此处阅读有关推断器的更多信息:https://refine.dev/docs/packages/documentation/inferencer/
如果一切正常,那么我们可以继续开发设置并开发我们的应用程序。
后端 - 发展
***** 如果快速运行的Docker容器仍在运行,请关闭它们,以避免在开发阶段发生任何碰撞! *****前往您的后端目录(例如my-app)并运行:
docker compose down
**************************************************************************************************************************************************** ****************************************************** ****************************************************** ****************************************************** ****************************************************** ****************************************************** ** 如果前端正在运行它也可能会引起一些混乱,那么通过按下(ctrl+c或适合您的操作系统的适当快捷方式)的简单退出预览服务器。 *** ****************************************************** ****************************************************** ****************************************************** ****************************************************** ****************************************************** *********************************************************
在此步骤中,我们将假设您已经克隆了nestjs-poliath-boilerplate
项目。在IDE中打开您的后端目录。
为了开始开发,我们需要更新两个环境变量(因为我们将使用NPM在本地计算机上运行Nest.js项目,但是我们的数据库和某些开发工具将在Docker中。打开您的.env文件和;
- 将
MAIL_HOST=maildev
el更改为country5
运行Docker容器:
docker compose up -d postgres adminer maildev
请注意,此命令不会启动我们的api
容器。
在本地运行Nest.js项目
由于我们正在本地机器上运行项目,因此我们必须安装依赖项,运行播种机并在开发环境中开始。
npm install
npm run migration:run
npm run seed:run
npm run start:dev
这将启动我们的后端,您应该在终端中看到日志。
前端 - 开发
在开发模式下运行前端非常简单。打开IDE中的前端目录。在终端中,运行以下命令:
npm run dev
这将启动开发服务器,它将向您显示其当前正在运行的端口。如果一切都很好,您应该能够登录并查看您的应用程序。
后端 - 添加新资源
现在,我们将为后端添加新资源。为此,它将是一个简单的任务,它将具有:ID,标题,完成,创建,在作者,受让人。
我们将在大多数工作中使用Nest CLI,所以让我们安装它:
npm install -g @nestjs/cli
NEST提供CRUD GENETARE作为其CLI工具的一部分。它为您的资源生成了起点。让我们生成我们的任务资源:
nest g resource
这将启动一个简单的向导:
? What name would you like to use for this resource (plural, e.g., "users")? tasks
接下来,选择“转运层的REST API”。
选择是生成Crud入口点的是
**************************************** 这将为我们的任务生成所有需要的文件在任务目录下的资源。 NEST不会默认创建DTO,它将仅创建一个我们应该在数据中填充的简单类。 ************************************************************************* *********
注意:不是为每个模型创建数据库表,而是针对那些被称为实体的模型。要将模型声明为实体,我们只需要添加 @entity()''在定义我们的模型的类声明之前的装饰器。
import {
Column,
CreateDateColumn,
Entity,
ManyToOne,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
import { User } from '../../users/entities/user.entity';
@Entity()
export class Task {
@PrimaryGeneratedColumn()
id: number;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt?: Date;
@Column({ type: String, nullable: false })
title: string;
@Column({ default: false })
done: boolean;
@ManyToOne(() => User, (user) => user.tasks)
author: User;
@ManyToOne(() => User, (user) => user.tasks)
assignee: User;
}
在上述代码中,我们创建了我们的任务模型。 Nest提供了多个装饰器,例如 * @CreateDateColumn()
*,它们将在创建实体时自动添加日期。 Additionally *********************************PrimaryGeneratedColumn* *****************************************。我们可以通过各种********* 列 *********诸如类型或默认值之类的选项。
此外,我们有************************************************************************************************************** **关系,因为用户可以是许多任务的作者,并且用户可以拥有多个任务,在我们的情况下,在我们的情况下,任务只有一个受让人。
此外,由于我们有用于用户的外国关系,因此我们必须将其添加到************** user.entity.ts: < /strong> *************
@OneToMany(() => Task, (task) => task.author)
tasks?: Task[];
@OneToMany(() => Task, (task) => task.assignee)
assignedTasks?: Task[];
接下来,我们必须创建一个迁移才能将我们的更改应用于数据库表。
npm run migration:generate -- src/database/migrations/CreateTasks
此命令将在迁移下生成新的迁移文件。名称将为 接下来,我们必须运行迁移: 如果我们以前的迁移有问题,我们可以简单地将其恢复: 在我们的任务控制器类上方,我们必须添加以下内容: 控制器装饰器将定义该类充当控制器,这是任务路径的调用,该API的版本为1。********************************************************** ************************************************ 这也将被可见**************************。 使用Guards Decorator将定义警卫,在这种情况下只有JWT(因此,只有身份验证的用户才能访问此路线。此样板还支持******************* < strong> rolesguard ***************** - 与******* 角色 *******装饰器,以限制特定用户组的用户访问。 apitags定义了如何以摇摇晃力的方式调用此路线集。 apibearerauth定义该控制器需要携带者令牌才能访问。 由于我们选择了在资源创建期间生成CRUD入口点的是,因此每个方法都在控制器中定义,并具有自己的装饰器(帖子,get,delete,patch)。 致电********************* findall() ************ * 方法。在这种情况下,我们可以使用Postman致电koude8,它应该返回消息:此操作返回所有任务,因为我们的********************** ******* 尚未实施服务! ************************************************************ **** 如果它返回错误401,则意味着您没有提供携带者令牌,您将通过以下JSON主体致电koude9的POST请求来获得。 然后将您的携带者令牌添加到Postman的授权字段中。 如果要限制特定(或多个)角色的特定途径,则必须在该方法上方添加以下装饰器: 这将仅限于登录用户的用户的访问。而且,如果您尝试调用获取方法到上一个路线(使用管理员帐户登录时),您将获得以下错误: 数据传输对象基本上将我们的数据转换在我们的服务和数据库之间。在Typeorm中,它可以定义我们实体的结构和验证,在这种情况下是任务。 默认情况下,创建和更新会生成两个主要的DTO ,在某些情况下会有所不同。在我们的情况下,它将扩展我们的创建:dto: 您可以看到这是默认情况下已经生成的。 回到板条箱。 根据我们的任务实体,我们必须提供标题,作者(ID)和受让人(ID)。 为简单起见,我们的任务DTO看起来像这样: 基本上仅定义其中一个字段不能为空。有多个可以使用的装饰器和验证器。 为了实际使用我们的数据做点事,我们必须创建一些逻辑。逻辑在tasks.service.ts文件中处理。首先,我们将编写一个用于创建新任务的代码。 at。一开始,我们必须将存储库注入我们服务类的构造函数: 存储库是Typeorm的一部分,它基本上用于我们与数据库的交互。 为了插入我们的任务作者,我们必须将当前用户传递给服务,因此我们的创建方法看起来像这样: **************************************************************************************************************************************************** ****************************************************** ****************************************************** ** ,但这无法开箱即用,因为我们需要将用户对象传递给我们的创建方法。此样板包含当前返回用户的电流装饰器。 **************************************************************************************** ****************************************************** ****************************************************** ****************** 所以,编辑任务。controller.ts: 现在,我们可以使用以下JSON主体将发布请求发送到koude8路线: 这将把此任务分配给我们的用户ID = 2(John Doe),作者将是我们在用户中签名(admin)。 **************************************************************************************************************************************************** ****************************************************** **** 接下来,我们将创建用于获取所有任务的服务。 ***************************************************************** ****************************************************** ********************************** 为了做到这一点,我们必须编辑***** findall() >
这是一种简单的方法,可以调用********* find() *********,基本上是喜欢的 这将返回我们任务的JSON数组: 接下来,我们将实施************* findOne() ************** *为了获取特定任务数据的方法: 首先,我们将在控制器中更新我们的FindOne方法以通过 ******************************************************** 接下来我们将写我们服务中的更新方法: ******************************************** * 您可以看到,我们正在接受任务ID和有效负载,这基本上是部分DTO,此方法返回我们更新的任务。 *************** 接下来,我们将实施删除服务 *************************************************** 这是非常简单的方法: 请注意,这些服务有各种各样的实现,我提供了简单的服务,有多种更新和处理错误,响应等的方法。但是可以根据特定的用例来实现。 一旦我们启动并运行了后端,我们就可以实现前端。 我们将使用************* 完善Cli *************新资源。使用Refine Create Command创建项目时,将安装完善的CLI,但在此样板中已经可用。 将终端定位到前端目录(例如my-app)并运行以下命令: 此向导会要求您定义您的资源名称(在我们的情况下为任务),并随身携带所有页面。这将生成******* 任务 *******目录,并带有所需的所有页面。另外,它将使用新的资源数据更新app.tsx。 请注意,此命令不会生成路由,并且仅在生成页面中使用推论器。 **************************************************************************************************************************************************** ************************** 首先,我们将为我们的任务添加路线 **** ****************************************************** **************** 这些路线与我们的页面直接相关,但是由于RBAC限制,它们将立即看到。 我们必须编辑********* casbin *********访问控制。打开 这将使用户和管理员都能进行所有CRUD操作。 现在,如果您在Localhost上刷新前端,然后单击任务,则应查看任务列表。 **************************************************************************************************************************************************** **** 如果未自动添加,请在app.tsx中的任务资源添加以下内容: ********************************************************************************* ****************************************** 所有页面都是使用推论器生成的,一个来自Refine.dev的零件:https://refine.dev/docs/packages/documentation/inferencer 如果您单击“推论器”弹出,您将看到有关任务列表的生成代码,复制此代码并将其粘贴到tasks/list.tsx(替换当前代码)中。弹出将消失,您将具有自定义页面的灵活性。 如果您的控制台中存在错误或您的页面空白,则页面导出可能是错误的(任务清单而不是Taskslist),请检查此类问题。 导出后,我们可以为页面编辑翻译以显示真实值而不是对象字段。打开 将以下对象添加到与用户相同的级别: 此外,在DocumentTitle下,与用户相同的级别添加以下级别: 上面用于页面标题。 为其他语言添加相同的字段。 当您刷新页面时,您应该看到字段现在的名称。 这很简单,只需单击创建按钮并填写字段即可。 * 当然,您可以将推论器代码 *复制到您的create.tsx文件,例如在字段上删除并更新了,因为这些文件已在我们的后端。 单击“保存” - 您将获得错误422-无法处理的实体,这是因为我们没有传递受让人参数。但是如何获得呢? 我们可以使用************ Uselist ***********************是一个粗略的选择,因为它将所有用户加载到内存中,并且我们正在客户端进行解析。相反,我们应该使用USEMANY,它将调用我们的后端方法并仅检索我们实际需要的过滤用户。 精炼提供了一些解决方案,例如数据提供商,但有时该代码不满足我们的需求。这就是为什么存在Swizzle的原因,它基本上会根据现有的预定义代码生成可以自定义的代码。 这就是我们可以编辑组件,验证页面,验证提供商等的方式 https://refine.dev/docs/tutorial/understanding-dataprovider/swizzle/#what-is-swizzle 此样板已实施了电子邮件服务,并且在 后端带有方便的开发工具,例如: 请注意,这些服务应在生产中禁用! 完成后端逻辑后,可以使用Docker在生产中运行它。您应该禁用上述开发工具。 请注意,如果您之前正在运行此样板,以获取代码,迁移等的最新更新。您首先必须重建Docker Image! 默认情况下实现了记录,并且在 请注意,您可能需要在很多情况下编辑默认代码(例如,更改查询方法,更新当前DTO等),但这是任何项目的绝佳起点。 此外,此文档将根据需要进行更新或修复,并提供新功能,修复和改进。 请注意,此文档主要旨在加快您的入职过程。虽然它可能提供一些“快捷方式”解决方案,但这些解决方案纯粹是为了说明目的,以帮助阐明该样板的工作。如果您是否在前端或后端遇到任何问题,我们鼓励您在GitHub上提出一个问题:
npm run migration:run
npm run migration:revert
@Controller({
path: 'tasks',
version: '1',
})
@UseGuards(AuthGuard('jwt'))
@ApiTags('Tasks')
@ApiBearerAuth()
控制器中的方法
{
"email": "admin@example.com",
"password" : "secret"
}
@Roles(RoleEnum.user)
@UseGuards(AuthGuard('jwt'), RolesGuard)
{
"message": "Forbidden resource",
"error": "Forbidden",
"statusCode": 403
}
dto
export class UpdateTaskDto extends PartialType(CreateTaskDto) {}
import { IsNotEmpty } from 'class-validator';
export class CreateTaskDto {
@IsNotEmpty()
title: string;
@IsNotEmpty()
author: number;
@IsNotEmpty()
assignee: number;
}
服务
constructor(
@InjectRepository(Task)
private tasksRepository: Repository<Task>,
) {}
create(createTaskDto: CreateTaskDto, user: User) {
createTaskDto.author = user;
const newTask = this.tasksRepository.save(
this.tasksRepository.create(createTaskDto),
);
return newTask;
}
@Post()
create(@Body() createTaskDto: CreateTaskDto, @CurrentUser() user: User) {
return this.tasksService.create(createTaskDto, user);
}
{
"title": "This is a task 4",
"assignee": 2
}
findAll() {
return this.tasksRepository.find();
}
SELECT * FROM TASKS;
现在我们可以在koude8端点上调用获取方法。
[
{
"id": 1,
"createdAt": "2023-08-15T10:18:14.437Z",
"updatedAt": "2023-08-15T10:18:14.437Z",
"title": "This is a task 1",
"done": false
},
{
"id": 2,
"createdAt": "2023-08-15T16:45:51.630Z",
"updatedAt": "2023-08-15T16:45:51.630Z",
"title": "This is a task 2",
"done": false
}
]
@Get(':id')
findOne(@Param('id') id: string) {
return this.tasksService.findOne({ id: +id });
}
{id: +id}
,因为我们将使用************ entityCondition ******* ******通过字段。最后,我们的服务中的发现方法将看起来像这样:
findOne(fields: EntityCondition<Task>): Promise<NullableType<Task>> {
return this.tasksRepository.findOne({ where: fields });
}
update(id: Task['id'], payload: DeepPartial<Task>): Promise<Task> {
return this.tasksRepository.save(
this.tasksRepository.create({
id,
...payload,
}),
);
}
remove(id: number) {
return this.articlesRepository.delete(id);
}
前端 - 添加新资源
npm run refine create-resource
<Route path="/tasks">
<Route index element={<TasksList />} />
<Route path="create" element={<TasksCreate />} />
<Route path="edit/:id" element={<TasksEdit />} />
<Route path="show/:id" element={<TasksShow />} />
</Route>
src/casbin/accessControl.ts
并将以下内容添加到适配器:
p, 1, tasks, (list)|(create)|(edit)|(show)|(delete)
p, 2, tasks, (list)|(create)|(edit)|(show)|(delete)
meta: {
canDelete: true,
},
I18N-翻译
public/locales/en/common.json
"tasks": {
"tasks": "Tasks",
"fields": {
"id": "ID",
"title": "Title",
"done": "Done",
"createdAt": "Created at",
"updatedAt": "Updated at"
},
"titles": {
"create": "Create task",
"edit": "Edit task",
"list": "Tasks",
"show": "Show task"
}
},
"tasks": {
"list": "Tasks | Poliath Manager",
"show": "#{{id}} Show task | Poliath Manager",
"edit": "#{{id}} Edit task | Poliath Manager",
"create": "Create new task | Poliath Manager",
"clone": "#{{id}} Clone task | Poliath Manager"
},
通过我们的前端创建新任务
const { data: data, isLoading } = useList<IUser>({
resource: 'users'
});
挥舞着
npm run refine swizzle
发送电子邮件
src/mail/mail.service.ts
中实现了用法,可以用作自定义的参考。
其他工具和信息
在生产中运行
docker compose build --no-cache
./logs
目录中可用日志。
结论
问题和错误