This is guide for developers to setup the development environment. Before you start to get prepared for the develop environment, you need to make sure your basic tools are installed correctly.
- Nodejs (Notice: To build the project, the right version is needed, otherwise installation can not be corrected, see .node-version)
- pnpm
- docker
-
copy
.env.dev
to.env
-
install dependencies
pnpm i
-
install dababase postgres with vector plugin
docker pull ankane/pgvector
-
start postgres db in docker
docker run --name callgent-postgres -p 5432:5432 -e POSTGRES_PASSWORD=postgres -e PG_VECTOR_EXTENSION=true -d ankane/pgvector
-
init db
npx prisma generate # generate PrismaClient npx prisma migrate dev # init db schema npx prisma db seed # init db data
-
init db test data
npx prisma migrate reset # reset db to initial state pnpm run prisma:seed-test # init db test data
-
start server
pnpm run start:dev
If all the above steps are done, and nothing failed, you can access the API at http://localhost:3000/api
-
run tests
pnpm run test:e2e
-
init project
pnpm i -g @nestjs/cli nest new callgent-api cd callgent-api
-
automatically setup the library, scripts and Docker files
nest add nestjs-prisma
-
integrate prisma plugins
-
ReposModule
-
init db
npx prisma init
-
create prisma schema, then init db
npx prisma migrate dev --name init
- write default value for
tenancy.tenantPk
in db
tenantPk Int @default(dbgenerated("(current_setting('tenancy.tenantPk'))::int"))
-
enable postgres row level security(RLS), so that we can filter data by
tenantPk
automatically: config in prisma/migrations/01_row_level_security/migration.sql, @see https://github.com/prisma/prisma-client-extensions/tree/main/row-level-security -
set
tenantPk
intocls
context:cls.set('TENANT_ID', ..
-
extend
PrismaClient
to settenantPk
before any querySELECT set_config('tenancy.tenantPk', cls.get('TENANT_ID') ...
-
bypass rls, for example, by admin, or looking up the logon user to determine their tenant ID:
CREATE POLICY bypass_rls_policy ON "User" USING (current_setting('tenancy.bypass_rls', TRUE)::text = 'on');
then when you want to bypass rls, you must set
tenancy.bypass_rls
toon
before running the query:await prisma.$executeRaw`SELECT set_config('tenancy.bypass_rls', 'on', TRUE)`;
there is no account type, you may invite members to your account if you have permission. TODO: you may set a mail host as default members. you may bind many identities to your user, which is useful to access oauth provider resources,
all validation is based on bearer token, with payload:
{
sub: user.pk.toString(),
iss: user.tenantPk.toString(),
aud: user.id,
}
extract token from header:
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromExtractors([
// ExtractJwt.fromAuthHeaderAsBearerToken(), // For bearer token
(request) => request?.headers['x-callgent-authorization'],
})}
-
config swagger
const config = new DocumentBuilder() // ... // .addBearerAuth(schema, 'defaultBearerAuth') .addSecurity('defaultBearerAuth', { type: 'apiKey', in: 'header', name: 'x-callgent-authorization', }) .build();
-
set default token for API test
const devJwtToken = 'test-jwt-token'; const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('docs/api', app, document, { swaggerOptions: { authAction: { defaultBearerAuth: { schema: { type: 'apiKey', in: 'header' }, value: devJwtToken, }, }, }, });
-
On controller, change from
@ApiBearerAuth('defaultBearerAuth')
to@ApiSecurity('defaultBearerAuth')
.
login with email and password, TODO: email verification is required.
need NOT scope to get user email:
- google: add 'https://www.googleapis.com/auth/userinfo.email' scope on oauth screen https://console.cloud.google.com/apis/credentials/consent/edit?hl=zh-cn&project=skilled-bonus-381610
- github, contains email by default, https://docs.github.com/en/rest/users/users?apiVersion=2022-11-28#get-the-authenticated-user
- facebook?: add 'email' scope on oauth screen https://developers.facebook.com/docs/facebook-login/permissions/overview
-
create a oauth client in 3rd party provider, e.g. github, get client id and secret
-
in
.env
add:`PROVIDER_KEY`_OAUTH_CLIENT_ID = `PROVIDER_KEY`_OAUTH_CLIENT_SECRET =
-
add provider config in
oauth-client.module.ts
-
add user info retrieval logic in
AutLoginListener#retrieveUserInfoFromOauth()
-
finally,
/api/auth/{PROVIDER_KEY}
is ready to use
We defined a parameter validator EntityIdExists
to check if an entity exists in the database.
You may annotate it to your DTO property like this:
export class CreateTaskDto {
// ...
// automatically validation check if the callgent exists in db on controller requesting
@EntityIdExists('callgent', 'id') // @EntityIdExists('entityType', 'fieldName')
callgentId: string;
}
Based on prisma-generator-nestjs-dto
, you may also annotate this decorator in schema.prisma
file:
model Task {
// ...
/// @CustomValidator(EntityIdExists, 'callgent', 'id', ../../infra/repo/validators/entity-exists.validator)
callgentId String @db.VarChar(36)
}
This makes the generated DTO to be annotated with @EntityIdExists
decorator.
This makes sure the callgentId
field is a valid UUID of a callgent in the database.
you can retrieve the entity instance directly from the dto:
const callgent = EntityIdExists.entity<Callgent>(dto, 'callgentId') ||
(await prisma.callgent.findUnique({ where: {id: dto.callgentId} }));