diff --git a/docs/menus.ts b/docs/menus.ts index 6ca303a0d..ca633fc29 100644 --- a/docs/menus.ts +++ b/docs/menus.ts @@ -3,6 +3,7 @@ export default { { title: 'Welcome', 'title.zh-CN': '欢迎', + 'title.tr-TR': 'Hoşgeldiniz', type: 'group', children: [ '/welcome/introduction/index', @@ -14,11 +15,13 @@ export default { { title: 'Getting started', 'title.zh-CN': '快速开始', + 'title.tr-TR': 'Başlangıç', type: 'group', children: [ { title: 'Installation', 'title.zh-CN': '安装', + 'title.TR-TR': 'Kurulum', type: 'subMenu', children: [ '/welcome/getting-started/installation/index', @@ -30,6 +33,7 @@ export default { { title: 'Upgrading', 'title.zh-CN': '升级', + 'title.TR-TR': 'Güncelleme', type: 'subMenu', children: [ '/welcome/getting-started/upgrading/index', @@ -43,12 +47,14 @@ export default { { title: 'Release', 'title.zh-CN': '版本发布', + 'title.TR-TR': 'Sürüm', type: 'group', children: ['/welcome/release/index', '/welcome/release/v08-changelog'], }, { title: 'Community', 'title.zh-CN': '社区', + 'title.TR-TR': 'Topluluk', type: 'group', children: [ '/welcome/community/contributing', @@ -63,6 +69,7 @@ export default { { title: 'Quick Start', 'title.zh-CN': '快速上手', + 'title.TR-TR': 'Hızlı Başlangıç', type: 'group', children: [ '/manual/quick-start/the-first-app', @@ -74,6 +81,7 @@ export default { { title: 'Core Concepts', 'title.zh-CN': '核心概念', + 'title.TR-TR': 'Temel Kavramlar', type: 'group', children: [ '/manual/core-concepts/a-b-c', @@ -87,6 +95,7 @@ export default { { title: 'Blocks Guide', 'title.zh-CN': '区块指南', + 'title.TR-TR': 'Blok Rehberi', type: 'group', children: ['/manual/blocks-guide/charts'], }, @@ -95,6 +104,7 @@ export default { { title: 'Getting started', 'title.zh-CN': '快速开始', + 'title.TR-TR': 'Başlarken', type: 'group', children: [ '/development/index', @@ -108,12 +118,14 @@ export default { { title: 'Server', 'title.zh-CN': '服务端', + 'title.TR-TR': 'Sunucu', type: 'group', children: [ '/development/server/index', { title: 'Collections & Fields', 'title.zh-CN': '数据表和字段', + 'title.TR-TR': 'Koleksiyonlar & Alanlar', type: 'subMenu', children: [ '/development/server/collections/index', @@ -137,11 +149,13 @@ export default { { title: 'Client', 'title.zh-CN': '客户端', + 'title.TR-TR': 'Ziyaretçi(Client)', type: 'group', children: [ '/development/client/index', { title: 'UI 设计器', + 'title.TR-TR': 'Kullanıcı Arayüz Tasarımcısı', type: 'subMenu', children: [ // '/development/client/ui-schema-designer/index', @@ -228,6 +242,8 @@ export default { { title: 'SchemaDesigner', 'title.zh-CN': 'SchemaDesigner', + 'title.TR-TR': 'Şema Tasarımcısı', + type: 'subMenu', children: [ '/api/client/schema-designer/schema-component', @@ -238,6 +254,7 @@ export default { { title: 'Extensions', 'title.zh-CN': 'Extensions', + 'title.TR-TR': 'Eklentiler', type: 'subMenu', children: [ // '/api/client/extensions/schema-component', diff --git a/docs/tr-TR/api/acl/acl-resource.md b/docs/tr-TR/api/acl/acl-resource.md new file mode 100644 index 000000000..8a79b4ca8 --- /dev/null +++ b/docs/tr-TR/api/acl/acl-resource.md @@ -0,0 +1,57 @@ +# Erişim Kontrol Listesi Kaynağı(ACL) + +Erişim Kontrol Listesi Kaynağı, Erişim Kontrol Listesi sistemindeki kaynak sınıfıdır. Erişim Kontrol Listesi sisteminde, kullanıcılara izinler verildiğinde ilgili kaynaklar otomatik olarak oluşturulur. + + +## Sınıf yönetimi + +### `constructor()` +Yapıcı Metod + +**Kullanım** +* `constructor(options: AclResourceOptions)` + +**Tür** +```typescript +type ResourceActions = { [key: string]: RoleActionParams }; + +interface AclResourceOptions { + name: string; // Kaynak adı + role: ACLRole; // Kaynağın Rolü + actions?: ResourceActions; +} +``` + +**Detaylar** + +`RoleActionParams` için [`aclRole.grantAction`](./acl-role.md#grantaction) adresine bakın + +### `getActions()` + +Kaynağın tüm eylemlerini alın ve sonucu bir `ResourceActions` nesnesi olarak döndürün. + +### `getAction()` +Adına göre Action'ın parametre yapılandırmasını döndürür ve döndürülen sonuç bir `RoleActionParams` nesnesidir. + +**Detaylar** + +`RoleActionParams` için [`aclRole.grantAction`](./acl-role.md#grantaction) adresine bakın + +### `setAction()` + +Kaynağın içinde bir Action parametresi yapılandırması ayarlayın ve sonucu bir `RoleActionParams` nesnesi olarak döndürün. + +**Kullanım** +* `setAction(name: string, params: RoleActionParams)` + +**Detaylar** + +* name - ayarlanacak eylem adı +* `RoleActionParams` için [`aclRole.grantAction`](./acl-role.md#grantaction) adresine bakın + +### `setActions()` + +**Kullanım** +* `setActions(actions: ResourceActions)` + +"setAction" toplu çağrısı için kolaylık yöntemi diff --git a/docs/tr-TR/api/acl/acl-role.md b/docs/tr-TR/api/acl/acl-role.md new file mode 100644 index 000000000..f9bdeb247 --- /dev/null +++ b/docs/tr-TR/api/acl/acl-role.md @@ -0,0 +1,87 @@ +# Erişim Kontrol Listesi Rol + +Erişim Kontrol Listesi Rolü, Erişim Kontrol Listesi sistemindeki kullanıcı rolü sınıfıdır. Erişim Kontrol Listesi sisteminde roller genellikle 'acl.define' kullanılarak tanımlanır. + +## Sınıf yönetimi + +### `constructor()` +Yapıcı metod + +**Kullanım** +* `constructor(public acl: ACL, public name: string)` + +**Detaylar** +* acl - ACL Örnek +* name - Rol adı + +### `grantAction()` + +Role İşlemler izni ver + +**Kullanım** +* `grantAction(path: string, options?: RoleActionParams)` + +**Tip** +```typescript +interface RoleActionParams { + fields?: string[]; + filter?: any; + own?: boolean; + whitelist?: string[]; + blacklist?: string[]; + [key: string]: any; +} +``` + +**Detaylar** + +* yol - kaynak Eylem yolu. Örneğin "gönderiler" kaynağının "düzenle" Eylemini temsil eden, kaynak adı ile Eylem arasında bir ":" iki nokta üst üste işaretiyle ayrılır. "gönderiler:düzenle" gibi. + +RoleActionParams yetkilendirme olduğunda daha ayrıntılı izin kontrolü elde etmek için ilgili eylemin yapılandırılabilir parametreleri kullanılır. + +* fields - erişilebilir alanlar + ```typescript + acl.define({ + role: 'admin', + actions: { + 'posts:view': { + // yönetici kullanıcılar gönderiler talep edebilir: işlemi görüntüleme, ancak yalnızca alanlar tarafından yapılandırılmış alan izinlerine sahip olma + fields: ["id", "title", "content"], + }, + }, + }); + ``` +* filter - İzin kaynağı filtreleme yapılandırması + ```typescript + acl.define({ + role: 'admin', + actions: { + 'posts:view': { + // Yönetici kullanıcı posts:view eylemini talep edebilir, ancak listelenen sonuçların filtre tarafından belirlenen koşulları karşılaması gerekir. + filter: { + createdById: '{{ ctx.state.currentUser.id }}', // Şablon sözdizimini destekleyen izin değerlendirildiğinde değiştirilecek olan değeri ctx cinsinden alabilirsiniz. + }, + }, + }, + }); + ``` +* Kendi - Yalnızca kendi verilerinize erişip erişemeyeceğinizi + ```typescript + const actionsWithOwn = { + 'posts:view': { + "own": true // + } + } + + // Eşittir + const actionsWithFilter = { + 'posts:view': { + "filter": { + "createdById": "{{ ctx.state.currentUser.id }}" + } + } + } + ``` +* beyaz liste - beyaz liste, yalnızca beyaz listedeki alanlara erişilebilir +* kara liste - kara liste, kara listedeki alanlara erişilemez + diff --git a/docs/tr-TR/api/acl/acl.md b/docs/tr-TR/api/acl/acl.md new file mode 100644 index 000000000..7882f9037 --- /dev/null +++ b/docs/tr-TR/api/acl/acl.md @@ -0,0 +1,232 @@ +# Erişim Kontrol Listesi + +Erişim Kontrol Listesi, Nocobase'deki izin kontrol modülüdür. Erişim Kontrol Listesinde rolleri, kaynakları kaydettikten ve karşılık gelen izinleri yapılandırdıktan sonra, rollerin izinlerini değerlendirebilirsiniz. + +## Kavram Açıklaması + +* rol (`ACLRole`): izin kararının nesnesi +* Kaynak (`ACLResource`): Nocobase Erişim Kontrol Listesinde, kaynak genellikle Restful API'deki Resource'a kavramsal olarak benzer olabilen bir veritabanı tablosuna karşılık gelir. +* İşlem: "Oluştur", "oku", "güncelle", "sil" vb. gibi kaynaklar üzerindeki işlemler. +* Strateji (`ACLAvailableStrategy`): Genellikle her rolün varsayılan kullanıcı izinlerini tanımlayan kendi izin stratejisi vardır. +* Grant: `Action`a rol erişimi vermek için `ACLRole` örneğinde `grantAction` işlevini çağırın. +* Kimlik Doğrulama: `ACL` örneğinde `can` işlevi çağrılır ve işlev tarafından döndürülen sonuç, kullanıcının kimlik doğrulama sonucudur. + + +## sınıf yöntemi + +### `constructor()` + +Bir `ACL` örneği oluşturan oluşturucu. + +```typescript +import { ACL } from '@nocobase/database'; + +const acl = new ACL(); +``` + +### `define()` + +Bir ACL rolü tanımlayın + +**Kullanım** +* `define(options: DefineOptions): ACLRole` + +**Tip** + +```typescript +interface DefineOptions { + role: string; + allowConfigure?: boolean; + strategy?: string | AvailableStrategyOptions; + actions?: ResourceActionsOptions; + routes?: any; +} +``` + +**Detaylar** + +* `role` - 角色名称 + +```typescript +// Yönetici adında bir rol tanımlayın +acl.define({ + role: 'admin', +}); +``` + +* `allowConfigure` - Yapılandırma izinlerine izin verilip verilmeyeceği +* `strategy` - Rol İzin Politikası + * Tanımlanan stratejinin kullanılacağını belirten, kullanılacak stratejinin adı olan bir `string` olabilir. + * `AvailableStrategyOptions` için bu rol için yeni bir strateji tanımlanabilir, bkz. [`setAvailableActions()`](#setavailableactions). +* `işlemler` - Bir rol tanımlarken, rol için erişilebilir bir `işlemler` nesnesi iletebilirsiniz. + Bundan sonra, kaynak izinlerini vermek için sırayla `aclRole.grantAction` çağrılır. Bkz. [`aclRole.grantAction`](./acl-role.md#grantaction) + +```typescript +acl.define({ + role: 'admin', + actions: { + 'posts:edit': {} + }, +}); +// Diğer kullanım şekli +const role = acl.define({ + role: 'admin', +}); + +role.grantAction('posts:edit', {}); +``` + +### `getRole()` + +Rol adına göre kayıtlı rol nesnesini döndürür + +**Kullanım** +* `getRole(name: string): ACLRole` + +### `removeRole()` + +Rol adına göre rolleri kaldırın + +**Kullanım** +* `removeRole(name: string)` + +### `can()` +Kimlik doğrulama işlevi + +**Kullanım** +* `can(options: CanArgs): CanResult | null` + +**Tip** + +```typescript +interface CanArgs { + role: string; // Rol ismi + resource: string; // Kaynak Adı + action: string; // İşlem adı +} + +interface CanResult { + role: string; // Rol ismi + resource: string; // Kaynak Adı + action: string; // İşlem adı + params?: any; // İzinler kaydedilirken gönderilen parametreler +} + +``` + +**Detaylar** + +`can` yöntemi öncelikle rolün kayıt için ilgili `Action` iznine sahip olup olmadığını belirleyecek ve değilse, rolün `strategy` eşleşip eşleşmediğini belirleyecektir. +Çağrı `null` döndürdüğünde, rolün izni olmadığını belirtir, aksi takdirde, rolün izne sahip olduğunu belirten bir `CanResult` nesnesi döndürür. + +**Örnek** +```typescript +// Rolleri tanımlayın, izinleri kaydedin +acl.define({ + role: 'admin', + actions: { + 'posts:edit': { + fields: ['title', 'content'], + }, + }, +}); + +const canResult = acl.can({ + role: 'admin', + resource: 'posts', + action: 'edit', +}); +/** + * canResult = { + * role: 'admin', + * resource: 'posts', + * action: 'edit', + * params: { + * fields: ['title', 'content'], + * } + * } + */ + +acl.can({ + role: 'admin', + resource: 'posts', + action: 'destroy', +}); // null +``` +### `use()` + +**Kullanım** +* `use(fn: any)` +Ara katman yazılımlarına ara katman işlevleri ekleyin. + +### `middleware()` + +`@nocobase/server` içinde kullanım için bir ara katman işlevi döndürür. Bu `ara yazılımı` kullandıktan sonra, `@nocobase/server`, her istek işlenmeden önce izin kararı verecektir. + +### `allow()` + +Kaynağı herkese açık hale getirin + +**Kullanım** +* `allow(resourceName: string, actionNames: string[] | string, condition?: string | ConditionFunc)` + +**Tip** +```typescript +type ConditionFunc = (ctx: any) => Promise | boolean; +``` + +**Detaylar** + +* resourceName - Kaynak Adı +* actionNames - kaynak işlem adı +* condition? - Konfigürasyon Etkili Koşullar + * `string` iletmek, tanımlanan koşulun kullanıldığını ve kayıt koşulunun `acl.allowManager.registerCondition` yöntemini kullandığını gösterir. + ```typescript + acl.allowManager.registerAllowCondition('superUser', async () => { + return ctx.state.user?.id === 1; + }); + + // superUser durumu için users:list yetkilerini açar + acl.allow('users', 'list', 'superUser'); + ``` + * ConditionFunc'u ctx parametresiyle iletir ve sonucunu `boolean` olarak döndürür. + ```typescript + // Kullanıcı kimliği 1 olduğunda, user:list'e erişebilirsiniz. + acl.allow('users', 'list', (ctx) => { + return ctx.state.user?.id === 1; + }); + ``` + +**Örnek** + +```typescript +// users:login erişim yetkisi olanlar erişebilir. +acl.allow('users', 'login'); +``` + +### `setAvailableActions()` + +**Kullanım** + +* `setAvailableStrategy(name: string, options: AvailableStrategyOptions)` + +Kullanılabilir bir izin politikası kaydedin + +**Tip** + +```typescript +interface AvailableStrategyOptions { + displayName?: string; + actions?: false | string | string[]; + allowConfigure?: boolean; + resource?: '*'; +} +``` + +**Detaylar** + +* displayName - Politika Adı +* allowConfigure - Bu politikanın **config source** izni olup olmadığına bakılmaksızın, bunu `true` olarak ayarladıktan sonra, `ACL`de bir `configResources` kaynağı olarak kaydolma iznini belirleme isteği bir geçiş döndürür. +* actions - Politika içindeki işlemler listesi joker karakteri `*` destekler +* resource - Politika içindeki kaynak tanımı joker karakteri `*` destekler + diff --git a/docs/tr-TR/api/actions.md b/docs/tr-TR/api/actions.md new file mode 100644 index 000000000..9c41e8e86 --- /dev/null +++ b/docs/tr-TR/api/actions.md @@ -0,0 +1,349 @@ +# Yerleşik ortak kaynak işlemleri + +NocoBase, CRUD gibi yaygın olarak kullanılan veri kaynaklarının işlemleri için yerleşik işlem yöntemlerine sahiptir ve ilgili işlemleri veri tablosu kaynakları aracılığıyla otomatik olarak eşler. + +Tüm işlem yöntemleri, kaynak sağlayıcı örneğine kaydedilir ve ayrıca standart Koa uyumlu ara yazılım işlevleridir (`(ctx, next) => Promise`). Eylemin parametreleri, rota tarafından ayrıştırılır ve 'ctx.action' nesnesine eklenir ve sonraki parametre açıklamaları bu nesneyi temel alır. + +Genellikle, yerleşik eylem yöntemini doğrudan çağırmaya gerek yoktur.Varsayılan eylem davranışını genişletmeniz gerektiğinde, özel eylem yönteminde varsayılan yöntemi çağırabilirsiniz. + +## Paket yapısı + +İlişkili varlıklar aşağıdaki şekillerde tanıtılabilir: + +```ts +import actions from '@nocobase/actions'; +``` + +## Tek Veri Kaynağı İşlemi + +### `list()` + +Bir veri listesi alınması işlemine karşılık gelen URL `GET /api/:list` şeklindedir. + +**参数** + +| parametre adı | tür | varsayılan değer | açıklama | +| --- | --- | --- | --- | +| `filter` | `Filter` | - | filtre parametresi | +| `fields` | `string[]` | - | Çekilecek alanlar | +| `except` | `string[]` | - | Çekilmeyecek alanlar | +| `appends` | `string[]` | - | İlişkisel alanlar | +| `sort` | `string[]` | - | Sıralama | +| `page` | `number` | 1 | Sayfalandırma | +| `pageSize` | `number` | 20 | Sayfa başına veri sayısı | + +**Örnek** + +Bir veri listesini sorgulamak için bir arabirim sağlamanız gerektiğinde fakat varsayılan olarak JSON biçiminde çıktı almadığınızda, onu yerleşik varsayılan yönteme göre genişletebilirsiniz: + +```ts +import actions from '@nocobase/actions'; + +app.actions({ + async ['books:list'](ctx, next) { + ctx.action.mergeParams({ + except: ['content'] + }); + + await actions.list(ctx, async () => { + const { rows } = ctx.body; + // JSON'u CSV çıktısına dönüştürün + ctx.body = rows.map(row => Object.keys(row).map(key => row[key]).join(',')).join('\n'); + ctx.type = 'text/csv'; + + await next(); + }); + } +}); +``` + +İstek örneği, CSV biçimindeki dosyanın dönüşünü alacaktır: + +```shell +curl -X GET http://localhost:13000/api/books:list +``` + +### `get()` + +Tek bir veri parçası almak için işlem URL'si `GET /api/:get` şeklindedir. + +**Parametre** + +| parametre adı | tür | varsayılan değer | açıklama | +| --- | --- | --- | --- | +| `filterByTk` | `number \| string` | - | birincil anahtarı filtrele | +| `filter` | `Filter` | - | filtre parametresi | +| `fields` | `string[]` | - | Çekilecek alanlar | +| `except` | `string[]` | - | Çekilmeyecek alanlar | +| `appends` | `string[]` | - | İlişkisel alanlar | +| `sort` | `string[]` | - | Sıralama | +| `page` | `number` | 1 | Sayfalandırma | +| `pageSize` | `number` | 20 | Sayfa başı veri sayısı | + +**Örnek** + +NocoBase'in yerleşik dosya yönetimi eklentisine dayanarak, istemci kaynak tanımlayıcılı bir dosya indirmeyi istediğinde dosya akışını döndürecek şekilde genişletilebilir: + +```ts +import path from 'path'; +import actions from '@nocobase/actions'; +import { STORAGE_TYPE_LOCAL } from '@nocobase/plugin-file-manager'; + +app.actions({ + async ['attachments:get'](ctx, next) { + ctx.action.mergeParams({ + appends: ['storage'], + }); + + await actions.get(ctx, async () => { + if (ctx.accepts('json', 'application/octet-stream') === 'json') { + return next(); + } + + const { body: attachment } = ctx; + const { storage } = attachment; + + if (storage.type !== STORAGE_TYPE_LOCAL) { + return ctx.redirect(attachment.url); + } + + ctx.body = fs.createReadStream(path.resolve(storage.options.documentRoot?, storage.path)); + ctx.attachment(attachment.filename); + ctx.type = 'application/octet-stream'; + + await next(); + }); + } +}); +``` + +İstek örneği, dosya akışının dönüşünü alacak: + +```shell +curl -X GET -H "Accept: application/octet-stream" http://localhost:13000/api/attachments:get?filterByTk=1 +``` + +### `create()` + +Tek bir veri parçası oluşturma işleminin URL'si "POST /api/:create" şeklindedir. + +**Parametre** + +| parametre adı | tür | varsayılan değer | açıklama | +| --- | --- | --- | --- | +| `values` | `Object` | - | oluşturulacak veriler | + +**Örnek** + +Dosya yönetimi eklentisine benzer şekilde, yüklenen dosyaya ek olarak ikili içerikli veri oluşturma: + +```ts +import multer from '@koa/multer'; +import actions from '@nocobase/actions'; + +app.actions({ + async ['files:create'](ctx, next) { + if (ctx.request.type === 'application/json') { + return actions.create(ctx, next); + } + + if (ctx.request.type !== 'multipart/form-data') { + return ctx.throw(406); + } + + // Dosya kaydetme işlemi yalnızca örnek olarak multer() kullanır ve tam mantığı temsil etmez + multer().single('file')(ctx, async () => { + const { file, body } = ctx.request; + const { filename, mimetype, size, path } = file; + + ctx.action.mergeParams({ + values: { + filename, + mimetype, + size, + path: file.path, + meta: typeof body.meta === 'string' ? JSON.parse(body.meta) : {}; + } + }); + + await actions.create(ctx, next); + }); + } +}); +``` + +İstek örneği; dosya tablosunda sıradan veriler oluşturabilir veya eklerle birlikte gönderebilirsiniz: + +```shell +# Yalnızca normal veriler oluşturun +curl -X POST -H "Content-Type: application/json" -d '{"filename": "some-file.txt", "mimetype": "text/plain", "size": 5, "url": "https://cdn.yourdomain.com/some-file.txt"}' "http://localhost:13000/api/files:create" + +# Eklerle birlikte gönder +curl -X POST -F "file=@/path/to/some-file.txt" -F 'meta={"length": 100}' "http://localhost:13000/api/files:create" +``` + +### `update()` + +Bir veya daha fazla veri parçasını güncellemek için kullanılan URL, "PUT /api/:update" şeklindedir. + +**Parametre** + +| parametre adı | tür | varsayılan değer | açıklama | +| --- | --- | --- | --- | +| `filter` | `Filter` | - | filtre parametresi | +| `filterByTk` | `number \| string` | - | birincil anahtarı filtrele | +| `values` | `Object` | - | güncellenecek veri değeri | + +Not: parametrelerdeki `filter` ve `filterByTk` en az bir öğe sağlar. + +**Örnek** + +"create()" örneğine benzer şekilde, güncellenmiş dosya olarak ikili içerik verilerini taşımak için de kullanılabilir. + +```ts +import multer from '@koa/multer'; +import actions from '@nocobase/actions'; + +app.actions({ + async ['files:update'](ctx, next) { + if (ctx.request.type === 'application/json') { + return actions.update(ctx, next); + } + + if (ctx.request.type !== 'multipart/form-data') { + return ctx.throw(406); + } + + // Dosya kaydetme işlemi yalnızca örnek olarak multer() kullanır ve tam mantığı temsil etmez + multer().single('file')(ctx, async () => { + const { file, body } = ctx.request; + const { filename, mimetype, size, path } = file; + + ctx.action.mergeParams({ + values: { + filename, + mimetype, + size, + path: file.path, + meta: typeof body.meta === 'string' ? JSON.parse(body.meta) : {}; + } + }); + + await actions.update(ctx, next); + }); + } +}); +``` + +İstek örneği, dosya tablosunda sıradan veriler oluşturabilir veya eklerle birlikte gönderebilirsiniz: + +```shell +# Yalnızca normal veriler oluşturun +curl -X PUT -H "Content-Type: application/json" -d '{"filename": "some-file.txt", "mimetype": "text/plain", "size": 5, "url": "https://cdn.yourdomain.com/some-file.txt"}' "http://localhost:13000/api/files:update" + +# Eklerle birlikte gönderme +curl -X PUT -F "file=@/path/to/some-file.txt" -F 'meta={"length": 100}' "http://localhost:13000/api/files:update" +``` + +### `destroy()` + +Bir veya daha fazla veri parçasını silin. Karşılık gelen URL "DELETE /api/:destroy" şeklindedir. + +**Parametre** + +| parametre adı | tür | varsayılan değer | açıklama | +| --- | --- | --- | --- | +| `filter` | `Filter` | - | filtre parametresi | +| `filterByTk` | `number \| string` | - | birincil anahtarı filtrele | + +Not: parametrelerdeki `filter` ve `filterByTk` en az bir öğe sağlar. + +**Örnek** + +Dosya verilerini silmek için bir dosya yönetimi eklentisinin uzantısına benzer şekilde dosyayı da silmek gerekir: + +```ts +import actions from '@nocobase/actions'; + +app.actions({ + async ['files:destroy'](ctx, next) { + // const repository = getRepositoryFromParams(ctx); + + // const { filterByTk, filter } = ctx.action.params; + + // const items = await repository.find({ + // fields: [repository.collection.filterTargetKey], + // appends: ['storage'], + // filter, + // filterByTk, + // context: ctx, + // }); + + // await items.reduce((promise, item) => promise.then(async () => { + // await item.removeFromStorage(); + // await item.destroy(); + // }), Promise.resolve()); + + await actions.destroy(ctx, async () => { + // yapılacak işler + await next(); + }); + } +}); +``` + +### `move()` +Karşılık gelen URL 'POST /api/:move' şeklindedir. + +Bu yöntem, verileri taşımak ve verilerin sırasını ayarlamak için kullanılır. Örneğin, bir sayfada bir öğeyi başka bir öğenin üstüne veya altına sürükleyip bırakın, sırayı ayarlamak için de bu yöntemi kullanabilirsiniz. + +**Parametre** + +| parametre adı | tür | varsayılan değer | açıklama | +|----------|-------------| -- |---------------| +| `sourceId` | `targetKey` | - | Kaynak öğe kimliği | +| `targetId` | `targetKey` | - | Hedef öğe kimliği | +| `sortField` | `string` | `sort` | Depolanan alan adını sırala | +| `targetScope` | `string` | - | Sıralanmış kapsam, bir kaynak farklı kapsamlara göre sıralanabilir | +| `sticky` | `boolean` | - | Hareketli elemanın üstte olup olmadığı | +| `method` | `insertAfter` \| `prepend` | - | Ekleme türü, hedef öğenin eklenmesinden önce veya sonra | + +## İlişkisel Kaynak Kaynak İşlemleri + +### `add()` + +Bir nesneyle ilişki eklemek için, karşılık gelen URL `POST /api/:add` şeklindedir. "hasMany" ve "belongsToMany" ilişkilendirmeleri için geçerlidir. + +**Parametre** + +| parametre adı | tür | varsayılan değer | açıklama | +|----------|-------------| --- | --- | +| `values` | `TargetKey \| TargetKey[]` | - | Eklenen ilişkili nesne kimliği | + +### `remove()` +Bir nesneyle olan ilişkilendirmeyi kaldırmak için karşılık gelen URL 'POST /api/:remove' şeklindedir. + +**Parametre** + +| parametre adı | tür | varsayılan değer | açıklama | +|----------|-------------| --- | --- | +| `values` | `TargetKey \| TargetKey[]` | - | Kaldırılan ilişkili nesne kimliği | + +### `set()` +İlişkili ilişkilendirme nesnesini ayarlamaya karşılık gelen URL 'POST /api/:set' şeklindedir. + +**Parametre** + +| parametre adı | tür | varsayılan değer | açıklama | +|----------|-------------| --- | --- | +| `values` | `TargetKey \| TargetKey[]` | - | İlişkili nesnenin kimliğini ayarlayın | + +### `toggle()` + +İlişkili ilişkilendirme nesnesini değiştirmeye karşılık gelen URL 'POST /api/:toggle' şeklindedir. "toggle", ilişkili nesnenin zaten var olup olmadığını dahili olarak belirler, varsa onu kaldırır ve yoksa ekler. + +**Parametre** + +| parametre adı | tür | varsayılan değer | açıklama | +|----------|-------------| -- | --- | +| `values` | `TargetKey` | - | Anahtarın ilişkili nesnesinin kimliği | diff --git a/docs/tr-TR/api/cli.md b/docs/tr-TR/api/cli.md new file mode 100644 index 000000000..5b45ad84c --- /dev/null +++ b/docs/tr-TR/api/cli.md @@ -0,0 +1,350 @@ +# NocoBase CLI + +NocoBase CLI, NocoBase uygulamalarını geliştirmenize, oluşturmanıza ve dağıtmanıza yardımcı olmak için tasarlanmıştır. + + + +NocoBase CLI, ts-node ve node çalışma modlarını destekler. + +- ts-node modu (varsayılan): geliştirme ortamı için gerçek zamanlı derlemeyi destekler, ancak yavaş yanıt verir +- node modu: üretim ortamında kullanılır, yanıt hızlıdır, ancak tüm kaynak kodunu derlemek için "yarn nocobase build"i çalıştırmanız gerekir + + + +## Kullanım talimatları + +```bash +$ yarn nocobase -h + +Kullanım: nocobase [komut] [seçenekler] + +Seçenekler: + -h, --help + +Komutlar: + console + db:auth Veritabanı bağlantısının başarılı olduğunu doğrulayın + db:sync Koleksiyon yapılandırması aracılığıyla ilgili veri tabloları ve alanları oluşturun + install Kurulum + start Uygulamayı üretim ortamında başlatır + build Derleme ve paketleme yapar + clean Derlenmiş dosyaları siler + dev Geliştirme ortamı için uygulamayı başlatın ve tam zamanında derlemeyi destekleyin + doc Belge geliştirme + test Test + umi + upgrade Sürüm yükseltme + migrator Veri aktarma + pm Eklenti yöneticisi + help +``` + +## Scaffold Uygulama + +Scaffold Uygulama `package.json` \ `scripts` aşağıdaki gibi: + +```json +{ + "scripts": { + "dev": "nocobase dev", + "start": "nocobase start", + "clean": "nocobase clean", + "build": "nocobase build", + "test": "nocobase test", + "pm": "nocobase pm", + "postinstall": "nocobase postinstall" + } +} +``` + +## Komut satırı uzantısı + +NocoBase CLI, [Komut Satırı](https://github.com/tj/commander.js) üzerine kurulmuştur, komutu özgürce genişletebilirsiniz, genişletilmiş komut 'app/server/index.ts' içinde yazılabilir: + +```ts +const app = new Application(config); + +app.command('hello').action(() => {}); +``` + +Veya bir eklenti için: + +```ts +class MyPlugin extends Plugin { + beforeLoad() { + this.app.command('hello').action(() => {}); + } +} +``` + +Terminal çalışması + +```bash +$ yarn nocobase hello +``` + +## Yerleşik komut satırı + +Kullanım sıklığına göre sırala + +### `dev` + +Geliştirme ortamında uygulamayı başlatın ve kod gerçek zamanlı olarak derlenir. + + +NocoBase kurulmadığında, otomatik olarak kurulacaktır (kurulum komutuna bakın) + + +```bash +Kullanım: nocobase dev [seçenekler] + +Seçenekler: + -p, --port [port] + --client + --server + -h, --help +``` + +Örnek + +```bash +# Uygulamayı başlatın, geliştirme ortamı için tam zamanında derleme +yarn nocobase dev +# Sadece sunucuyu başlat +yarn nocobase dev --server +# sadece istemciyi başlat +yarn nocobase dev --client +``` + +### `start` + +Üretim ortamında, uygulamayı başlatmak için kodun yarn build ile derlenmesi gereklidir. + + + +- NocoBase kurulmadığında, otomatik olarak kurulacaktır (kurulum komutuna bakın) +- Kaynak kodu değiştirildiğinde, yeniden paketlenmesi gerekir (build komutuna bakın) + + + +```bash +$ yarn nocobase start -h + +Kullanım: nocobase start [seçenekler] + +Seçenekler: + -p, --port + -s, --silent + -h, --help +``` + +Örnek + +```bash +# derlenen kodun kullanımı için sistemi başlatın, +yarn nocobase start +``` + +### `install` + +Kurulum + +```bash +$ yarn nocobase install -h + +Kullanım: nocobase install [seçenekler] + +Seçenekler: + -f, --force + -c, --clean + -s, --silent + -l, --lang [lang] + -e, --root-email + -p, --root-password + -n, --root-nickname [rootNickname] + -h, --help +``` + +Örnek + +```bash +# İlk kurulum +yarn nocobase install -l tr-TR -e admin@nocobase.com -p admin123 +# NocoBase için tüm veri sayfalarını kaldırın ve yeniden yükleyin +yarn nocobase install -f -l tr-TR -e admin@nocobase.com -p admin123 +# Veritabanını boşaltın ve yeniden yükleyin +yarn nocobase install -c -l tr-TR -e admin@nocobase.com -p admin123 +``` + + + +`-f/--force` ve `-c/--clean` +- `-f/--force` NocoBase verileri silin +- `-c/--clean` Veritabanını boşaltın, tüm veri tabloları silinecek + + + +### `upgrade` + +Sürüm yükseltme + +```bash +yarn nocobase upgrade +``` + +### `test` + +jest testi, "-c, --db-clean" desteğini genişletmenin yanı sıra tüm [jest-cli](https://jestjs.io/docs/cli) seçeneklerini destekler. + +```bash +$ yarn nocobase test -h + +Usage: nocobase test [seçenekler] + +Seçenekler: + -c, --db-clean Tüm testleri çalıştırmadan önce veritabanını temizleyin + -h, --help +``` + +Örnek + +```bash +# tüm test dosyalarını çalıştır +yarn nocobase test +# Belirtilen klasördeki tüm test dosyalarını çalıştırın +yarn nocobase test packages/core/server +# belirtilen dosyadaki tüm testleri çalıştır +yarn nocobase test packages/core/database/src/__tests__/database.test.ts + +# Testi çalıştırmadan önce veritabanını temizleyin +yarn nocobase test -c +yarn nocobase test packages/core/server -c +``` + +### `build` + +Kod üretim ortamına dağıtılmadan önce kaynak kodun derlenmesi ve paketlenmesi gerekir.Kod değiştirilirse yeniden oluşturulması gerekir. + +```bash +# Tüm paketler +yarn nocobase build +# Belirtilen paket(ler) +yarn nocobase build app/server app/client +``` + +### `clean` + +derlenmiş dosyaları sil + +```bash +yarn clean +# Aynı işi aşağıdaki kod ile de yapabilirsiniz. +yarn rimraf -rf packages/*/*/{lib,esm,es,dist} +``` + +### `doc` + +Döküman geliştirme + +```bash +# Belge geliştirmeyi başlar +yarn doc --lang=tr-TR # yarn doc dev ile aynı işi yapar +# Dokümantasyon oluşturun, varsayılan olarak ./docs/dist/ dizinine çıktı alın +yarn doc build +# Dist ile belge çıktısının son etkisini görüntüleyin +yarn doc serve --lang=tr-TR +``` + +### `db:auth` + +Veritabanı bağlantısının başarılı olduğunu doğrulayın + +```bash +$ yarn nocobase db:auth -h + +Kullanım: nocobase db:auth [seçenekler] + +Seçenekler: + -r, --retry [TekrarSayısı] TekrarSayısı yazan yere bir rakam yazın. + -h, --help +``` + +### `db:sync` + +Koleksiyon yapılandırması aracılığıyla veri tabloları ve alanlar oluşturun + +```bash +$ yarn nocobase db:sync -h + +Kullanım: nocobase db:sync [seçenek] + +Seçenekler: + -f, --force + -h, --help yardım içeriğini görüntüler +``` + +### `migrator` + +Veri aktarma + +```bash +$ yarn nocobase migrator + +Konumsal argümanlar: + + up Bekleyen taşımaları uygular + down Taşıma işlemlerini geri al + pending Bekleyen taşımaları listeler + executed Yürütülen taşımaları listeler + create Taşıma dosyası oluşturun +``` + +### `pm` + +Eklenti yöneticisi + +```bash +# Eklenti oluştur +yarn pm create hello +# Eklentiyi kaydet +yarn pm add hello +# Eklentiyi etkinleştir +yarn pm enable hello +# Eklentiyi devre dışı bırak +yarn pm disable hello +# Eklentiyi kaldır +yarn pm remove hello +``` + +Tamamlanmayanlar + +```bash +# Eklenti sürüm yükseltme +yarn pm upgrade hello +# Eklentiyi yayınla +yarn pm publish hello +``` + +### `umi` + +"app/client", [umi](https://umijs.org/) tabanlıdır ve ilgili diğer komutları "nocobase umi" aracılığıyla yürütebilir. + +```bash +# Geliştirme ortamı için gereken .umi önbelleğini oluşturun +yarn nocobase umi generate tmp +``` + +### `help` + +Yardım komutu, ayrıca `-h` ve `--help` seçenek parametresiyle de kullanılabilir + +```bash +# Tüm yardım içeriğini göster +yarn nocobase help +# Aynı içeriğe aşağıdaki komut ile de ulaşabilirsiniz. +yarn nocobase -h +# TAynı içeriğe aşağıdaki komut ile de ulaşabilirsiniz. +yarn nocobase --help +# db:sync komutunun seçeneklerini görüntüleyin +yarn nocobase db:sync -h +``` diff --git a/docs/tr-TR/api/client/application.md b/docs/tr-TR/api/client/application.md new file mode 100644 index 000000000..a7f126b4a --- /dev/null +++ b/docs/tr-TR/api/client/application.md @@ -0,0 +1,62 @@ +# Application + +## 构造函数 + +### `constructor()` + +创建一个应用实例。 + +**签名** + +* `constructor(options: ApplicationOptions)` + +**示例** + +```ts +const app = new Application({ + apiClient: { + baseURL: process.env.API_BASE_URL, + }, + dynamicImport: (name: string) => { + return import(`../plugins/${name}`); + }, +}); +``` + +## 方法 + +### use() + +添加 Providers,内置 Providers 有: + +- APIClientProvider +- I18nextProvider +- AntdConfigProvider +- RemoteRouteSwitchProvider +- SystemSettingsProvider +- PluginManagerProvider +- SchemaComponentProvider +- SchemaInitializerProvider +- BlockSchemaComponentProvider +- AntdSchemaComponentProvider +- ACLProvider +- RemoteDocumentTitleProvider + +### render() + +渲染 App 组件 + +```ts +import { Application } from '@nocobase/client'; + +export const app = new Application({ + apiClient: { + baseURL: process.env.API_BASE_URL, + }, + dynamicImport: (name: string) => { + return import(`../plugins/${name}`); + }, +}); + +export default app.render(); +``` \ No newline at end of file diff --git a/docs/tr-TR/api/client/extensions/acl.md b/docs/tr-TR/api/client/extensions/acl.md new file mode 100644 index 000000000..09d8e9a94 --- /dev/null +++ b/docs/tr-TR/api/client/extensions/acl.md @@ -0,0 +1,23 @@ +# ACL + +## Components + +### `` + +### `` + +### `` + +### `` + +### `` + +### `` + +## Hooks + +### `useACLContext()` + +### `useACLRoleContext()` + +### `useRoleRecheck()` diff --git a/docs/tr-TR/api/client/extensions/block-provider.md b/docs/tr-TR/api/client/extensions/block-provider.md new file mode 100644 index 000000000..6b3334a1a --- /dev/null +++ b/docs/tr-TR/api/client/extensions/block-provider.md @@ -0,0 +1,25 @@ +# BlockProvider + +## 内核方法 + +### `` + +### `useBlockRequestContext()` + +## 内置 BlockProvider 组件 + +### `` + +### `` + +### `` + +### `` + +### `` + +### `` + +### `` + +### `` diff --git a/docs/tr-TR/api/client/extensions/collection-manager.md b/docs/tr-TR/api/client/extensions/collection-manager.md new file mode 100644 index 000000000..d4f65dfbf --- /dev/null +++ b/docs/tr-TR/api/client/extensions/collection-manager.md @@ -0,0 +1,267 @@ +# CollectionManager + +## Components + +### CollectionManagerProvider + +```jsx | pure + +``` + +### CollectionProvider + +```jsx | pure +const collection = { + name: 'tests', + fields: [ + { + type: 'string', + name: 'title', + interface: 'input', + uiSchema: { + type: 'string', + 'x-component': 'Input' + }, + }, + ], +}; + +``` + +如果没有传 collection 参数,从 CollectionManagerProvider 里取对应 name 的 collection。 + +```jsx | pure +const collections = [ + { + name: 'tests', + fields: [ + { + type: 'string', + name: 'title', + interface: 'input', + uiSchema: { + type: 'string', + 'x-component': 'Input' + }, + }, + ], + } +]; + + + +``` + +### CollectionFieldProvider + +```jsx | pure +const field = { + type: 'string', + name: 'title', + interface: 'input', + uiSchema: { + type: 'string', + 'x-component': 'Input' + }, +}; + +``` + +如果没有传 field 参数,从 CollectionProvider 里取对应 name 的 field。 + +```jsx | pure +const collection = { + name: 'tests', + fields: [ + { + type: 'string', + name: 'title', + interface: 'input', + uiSchema: { + type: 'string', + 'x-component': 'Input' + }, + }, + ], +}; + + + +``` + +### CollectionField + +万能字段组件,需要与 `` 搭配使用,仅限于在 Schema 场景使用。从 CollectionProvider 里取对应 name 的 field schema。可通过 CollectionField 所在的 schema 扩展配置。 + +```ts +{ + name: 'title', + 'x-decorator': 'FormItem', + 'x-decorator-props': {}, + 'x-component': 'CollectionField', + 'x-component-props': {}, + properties: {}, +} +``` + +## Hooks + +### useCollectionManager() + +与 `` 搭配使用 + +```jsx | pure +const { collections, get } = useCollectionManager(); +``` + +### useCollection() + +与 `` 搭配使用 + +```jsx | pure +const { name, fields, getField, findField, resource } = useCollection(); +``` + +### useCollectionField() + +与 `` 搭配使用 + +```jsx | pure +const { name, uiSchema, resource } = useCollectionField(); +``` + +resource 需要与 `` 搭配使用,用于提供当前数据表行记录的上下文。如: + +# CollectionManager + +## Components + +### CollectionManagerProvider + +```jsx | pure + +``` + +### CollectionProvider + +```jsx | pure +const collection = { + name: 'tests', + fields: [ + { + type: 'string', + name: 'title', + interface: 'input', + uiSchema: { + type: 'string', + 'x-component': 'Input' + }, + }, + ], +}; + +``` + +如果没有传 collection 参数,从 CollectionManagerProvider 里取对应 name 的 collection。 + +```jsx | pure +const collections = [ + { + name: 'tests', + fields: [ + { + type: 'string', + name: 'title', + interface: 'input', + uiSchema: { + type: 'string', + 'x-component': 'Input' + }, + }, + ], + } +]; + + + +``` + +### CollectionFieldProvider + +```jsx | pure +const field = { + type: 'string', + name: 'title', + interface: 'input', + uiSchema: { + type: 'string', + 'x-component': 'Input' + }, +}; + +``` + +如果没有传 field 参数,从 CollectionProvider 里取对应 name 的 field。 + +```jsx | pure +const collection = { + name: 'tests', + fields: [ + { + type: 'string', + name: 'title', + interface: 'input', + uiSchema: { + type: 'string', + 'x-component': 'Input' + }, + }, + ], +}; + + + +``` + +### CollectionField + +万能字段组件,需要与 `` 搭配使用,仅限于在 Schema 场景使用。从 CollectionProvider 里取对应 name 的 field schema。可通过 CollectionField 所在的 schema 扩展配置。 + +```ts +{ + name: 'title', + 'x-decorator': 'FormItem', + 'x-decorator-props': {}, + 'x-component': 'CollectionField', + 'x-component-props': {}, + properties: {}, +} +``` + +## Hooks + +### useCollectionManager() + +与 `` 搭配使用 + +```jsx | pure +const { collections, get } = useCollectionManager(); +``` + +### useCollection() + +与 `` 搭配使用 + +```jsx | pure +const { name, fields, getField, findField, resource } = useCollection(); +``` + +### useCollectionField() + +与 `` 搭配使用 + +```jsx | pure +const { name, uiSchema, resource } = useCollectionField(); +``` + +resource 需要与 `` 搭配使用,用于提供当前数据表行记录的上下文。 \ No newline at end of file diff --git a/docs/tr-TR/api/client/extensions/schema-component.md b/docs/tr-TR/api/client/extensions/schema-component.md new file mode 100644 index 000000000..832818f75 --- /dev/null +++ b/docs/tr-TR/api/client/extensions/schema-component.md @@ -0,0 +1,14 @@ +# 适配的 Schema 组件 + +## Common + +- DndContext +- SortableItem + +## And Design + +- Action +- BlockItem +- Calendar +- CardItem +- Cascader diff --git a/docs/tr-TR/api/client/index.md b/docs/tr-TR/api/client/index.md new file mode 100644 index 000000000..3a2170da0 --- /dev/null +++ b/docs/tr-TR/api/client/index.md @@ -0,0 +1,3 @@ +# Overview + +test diff --git a/docs/tr-TR/api/client/route-switch.md b/docs/tr-TR/api/client/route-switch.md new file mode 100644 index 000000000..3aacb7083 --- /dev/null +++ b/docs/tr-TR/api/client/route-switch.md @@ -0,0 +1,79 @@ +# RouteSwitch + +## `` + +```ts +interface RouteSwitchProviderProps { + components?: ReactComponent; + routes?: RouteRedirectProps[]; +} +``` + +## `` + +```ts +interface RouteSwitchProps { + routes?: RouteRedirectProps[]; + components?: ReactComponent; +} + +type RouteRedirectProps = RedirectProps | RouteProps; + +interface RedirectProps { + type: 'redirect'; + to: any; + path?: string; + exact?: boolean; + strict?: boolean; + push?: boolean; + from?: string; + [key: string]: any; +} + +interface RouteProps { + type: 'route'; + path?: string | string[]; + exact?: boolean; + strict?: boolean; + sensitive?: boolean; + component?: any; + routes?: RouteProps[]; + [key: string]: any; +} +``` + +## 完整示例 + +```tsx | pure +import React from 'react'; +import { Link, MemoryRouter as Router } from 'react-router-dom'; +import { RouteRedirectProps, RouteSwitchProvider, RouteSwitch } from '@nocobase/client'; + +const Home = () =>

Home

; +const About = () =>

About

; + +const routes: RouteRedirectProps[] = [ + { + type: 'route', + path: '/', + exact: true, + component: 'Home', + }, + { + type: 'route', + path: '/about', + component: 'About', + }, +]; + +export default () => { + return ( + + + Home, About + + + + ); +}; +``` \ No newline at end of file diff --git a/docs/tr-TR/api/client/schema-designer/schema-component.md b/docs/tr-TR/api/client/schema-designer/schema-component.md new file mode 100644 index 000000000..61f646103 --- /dev/null +++ b/docs/tr-TR/api/client/schema-designer/schema-component.md @@ -0,0 +1,13 @@ +# SchemaComponent + +## 核心组件 + +### `` +### `` +### `` + +## 核心方法 + +### `createDesignable()` +### `useDesignable()` +### `useCompile()` diff --git a/docs/tr-TR/api/client/schema-designer/schema-initializer.md b/docs/tr-TR/api/client/schema-designer/schema-initializer.md new file mode 100644 index 000000000..1502fd496 --- /dev/null +++ b/docs/tr-TR/api/client/schema-designer/schema-initializer.md @@ -0,0 +1,27 @@ +# SchemaInitializer + +用于各种 schema 的初始化。新增的 schema 可以插入到某个已有 schema 节点的任意位置,包括: + +```ts +{ + properties: { + // beforeBegin 在当前节点的前面插入 + node1: { + properties: { + // afterBegin 在当前节点的第一个子节点前面插入 + // ... + // beforeEnd 在当前节点的最后一个子节点后面 + }, + }, + // afterEnd 在当前节点的后面 + }, +} +``` + +SchemaInitializer 的核心包括 `` 和 `` 两个组件。`` 用于创建 Schema 的下拉菜单按钮,下拉菜单的菜单项为 ``。 + +### `` + +### `` + +### `` diff --git a/docs/tr-TR/api/client/schema-designer/schema-settings.md b/docs/tr-TR/api/client/schema-designer/schema-settings.md new file mode 100644 index 000000000..b014e7435 --- /dev/null +++ b/docs/tr-TR/api/client/schema-designer/schema-settings.md @@ -0,0 +1,25 @@ +# SchemaSettings + +### `` + +### `` + +### `` + +### `` + +### `` + +### `` + +### `` + +### `` + +### `` + +### `` + +### `` + +### `` diff --git a/docs/tr-TR/api/database/collection.md b/docs/tr-TR/api/database/collection.md new file mode 100644 index 000000000..8be6651ea --- /dev/null +++ b/docs/tr-TR/api/database/collection.md @@ -0,0 +1,484 @@ +# Collection + +数据表结构管理类。 + +大部分接口通常不会直接由开发者调用,除非进行较底层的扩展开发。 + +## 构造函数 + +通常不会直接使用,主要通过 `Database` 实例的 `collection` 方法作为代理入口调用。 + +**签名** + +* `constructor(options: CollectionOptions, context: CollectionContext)` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options.name` | `string` | - | collection 标识 | +| `options.tableName?` | `string` | - | 数据库表名,如不传则使用 `options.name` 的值 | +| `options.fields?` | `FieldOptions[]` | - | 字段定义,详见 [Field](./field) | +| `options.model?` | `string \| ModelCtor` | - | Sequelize 的 Model 类型,如果使用的是 `string`,则需要调用之前在 db 上注册过该模型名称 | +| `options.repository?` | `string \| RepositoryType` | - | 数据仓库类型,如果使用 `string`,则需要调用之前在 db 上注册过该仓库类型 | +| `options.sortable?` | `string \| boolean \| { name?: string; scopeKey?: string }` | - | 数据可排序字段配置,默认不排序 | +| `options.autoGenId?` | `boolean` | `true` | 是否自动生成唯一主键,默认为 `true` | +| `context.database` | `Database` | - | 所在的上下文环境数据库 | + +**示例** + +创建一张文章表: + +```ts +const posts = new Collection({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + }, + { + type: 'double', + name: 'price', + } + ] +}, { + // 已存在的数据库实例 + database: db +}); +``` + +## 实例成员 + +### `options` + +数据表配置初始参数。与构造函数的 `options` 参数一致。 + +### `context` + +当前数据表所属的上下文环境,目前主要是数据库实例。 + +### `name` + +数据表名称。 + +### `db` + +所属数据库实例。 + +### `filterTargetKey` + +作为主键的字段名。 + +### `isThrough` + +是否为中间表。 + +### `model` + +匹配 Sequelize 的 Model 类型。 + +### `repository` + +数据仓库实例。 + +## 字段配置方法 + +### `getField()` + +获取数据表已定义对应名称的字段对象。 + +**签名** + +* `getField(name: string): Field` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `name` | `string` | - | 字段名称 | + +**示例** + +```ts +const posts = db.collection({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + } + ] +}); + +const field = posts.getField('title'); +``` + +### `setField()` + +对数据表设置字段。 + +**签名** + +* `setField(name: string, options: FieldOptions): Field` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `name` | `string` | - | 字段名称 | +| `options` | `FieldOptions` | - | 字段配置,详见 [Field](./field) | + +**示例** + +```ts +const posts = db.collection({ name: 'posts' }); + +posts.setField('title', { type: 'string' }); +``` + +### `setFields()` + +对数据表批量设置多个字段。 + +**签名** + +* `setFields(fields: FieldOptions[], resetFields = true): Field[]` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `fields` | `FieldOptions[]` | - | 字段配置,详见 [Field](./field) | +| `resetFields` | `boolean` | `true` | 是否重置已存在的字段 | + +**示例** + +```ts +const posts = db.collection({ name: 'posts' }); + +posts.setFields([ + { type: 'string', name: 'title' }, + { type: 'double', name: 'price' } +]); +``` + +### `removeField()` + +移除数据表已定义对应名称的字段对象。 + +**签名** + +* `removeField(name: string): void | Field` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `name` | `string` | - | 字段名称 | + +**示例** + +```ts +const posts = db.collection({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + } + ] +}); + +posts.removeField('title'); +``` + +### `resetFields()` + +重置(清空)数据表的字段。 + +**签名** + +* `resetFields(): void` + +**示例** + +```ts +const posts = db.collection({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + } + ] +}); + +posts.resetFields(); +``` + +### `hasField()` + +判断数据表是否已定义对应名称的字段对象。 + +**签名** + +* `hasField(name: string): boolean` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `name` | `string` | - | 字段名称 | + +**示例** + +```ts +const posts = db.collection({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + } + ] +}); + +posts.hasField('title'); // true +``` + +### `findField()` + +查找数据表中符合条件的字段对象。 + +**签名** + +* `findField(predicate: (field: Field) => boolean): Field | undefined` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `predicate` | `(field: Field) => boolean` | - | 查找条件 | + +**示例** + +```ts +const posts = db.collection({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + } + ] +}); + +posts.findField(field => field.name === 'title'); +``` + +### `forEachField()` + +遍历数据表中的字段对象。 + +**签名** + +* `forEachField(callback: (field: Field) => void): void` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `callback` | `(field: Field) => void` | - | 回调函数 | + +**示例** + +```ts +const posts = db.collection({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + } + ] +}); + +posts.forEachField(field => console.log(field.name)); +``` + +## 索引配置方法 + +### `addIndex()` + +添加数据表索引。 + +**签名** + +* `addIndex(index: string | string[] | { fields: string[], unique?: boolean,[key: string]: any })` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `index` | `string \| string[]` | - | 需要配置索引的字段名 | +| `index` | `{ fields: string[], unique?: boolean, [key: string]: any }` | - | 完整配置 | + +**示例** + +```ts +const posts = db.collection({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + } + ] +}); + +posts.addIndex({ + fields: ['title'], + unique: true +}); +``` + +### `removeIndex()` + +移除数据表索引。 + +**签名** + +* `removeIndex(fields: string[])` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `fields` | `string[]` | - | 需要移除索引的字段名组合 | + +**示例** + +```ts +const posts = db.collection({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + } + ], + indexes: [ + { + fields: ['title'], + unique: true + } + ] +}); + +posts.removeIndex(['title']); +``` + +## 表配置方法 + +### `remove()` + +删除数据表。 + +**签名** + +* `remove(): void` + +**示例** + +```ts +const posts = db.collection({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + } + ] +}); + +posts.remove(); +``` + +## 数据库操作方法 + +### `sync()` + +同步数据表定义到数据库。除了 Sequelize 中默认的 `Model.sync` 的逻辑,还会一并处理关系字段对应的数据表。 + +**签名** + +* `sync(): Promise` + +**示例** + +```ts +const posts = db.collection({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + } + ] +}); + +await posts.sync(); +``` + +### `existsInDb()` + +判断数据表是否存在于数据库中。 + +**签名** + +* `existsInDb(options?: Transactionable): Promise` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options?.transaction` | `Transaction` | - | 事务实例 | + +**示例** + +```ts +const posts = db.collection({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + } + ] +}); + +const existed = await posts.existsInDb(); + +console.log(existed); // false +``` + +### `removeFromDb()` + +**签名** + +* `removeFromDb(): Promise` + +**示例** + +```ts +const books = db.collection({ + name: 'books' +}); + +// 同步书籍表到数据库 +await db.sync(); + +// 删除数据库中的书籍表 +await books.removeFromDb(); +``` diff --git a/docs/tr-TR/api/database/field.md b/docs/tr-TR/api/database/field.md new file mode 100644 index 000000000..340b97cde --- /dev/null +++ b/docs/tr-TR/api/database/field.md @@ -0,0 +1,554 @@ +# Field + +数据表字段管理类(抽象类)。同时是所有字段类型的基类,其他任意字段类型均通过继承该类来实现。 + +## 构造函数 + +通常不会直接由开发者调用,主要通过 `db.collection({ fields: [] })` 方法作为代理入口调用。 + +在扩展字段时主要通过继承 `Field` 抽象类,再注册到 Database 实例中来实现。 + +**签名** + +* `constructor(options: FieldOptions, context: FieldContext)` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options` | `FieldOptions` | - | 字段配置对象 | +| `options.name` | `string` | - | 字段名称 | +| `options.type` | `string` | - | 字段类型,对应在 db 中注册的字段类型名称 | +| `context` | `FieldContext` | - | 字段上下文对象 | +| `context.database` | `Database` | - | 数据库实例 | +| `context.collection` | `Collection` | - | 数据表实例 | + +## 实例成员 + +### `name` + +字段名称。 + +### `type` + +字段类型。 + +### `dataType` + +字段数据库存储类型。 + +### `options` + +字段初始化配置参数。 + +### `context` + +字段上下文对象。 + +## 配置方法 + +### `on()` + +基于数据表事件的快捷定义方式。相当于 `db.on(this.collection.name + '.' + eventName, listener)`。 + +继承时通常无需覆盖此方法。 + +**签名** + +* `on(eventName: string, listener: (...args: any[]) => void)` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `eventName` | `string` | - | 事件名称 | +| `listener` | `(...args: any[]) => void` | - | 事件监听器 | + +### `off()` + +基于数据表事件的快捷移除方式。相当于 `db.off(this.collection.name + '.' + eventName, listener)`。 + +继承时通常无需覆盖此方法。 + +**签名** + +* `off(eventName: string, listener: (...args: any[]) => void)` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `eventName` | `string` | - | 事件名称 | +| `listener` | `(...args: any[]) => void` | - | 事件监听器 | + +### `bind()` + +当字段被添加到数据表时触发的执行内容。通常用于添加数据表事件监听器和其他处理。 + +继承时需要先调用对应的 `super.bind()` 方法。 + +**签名** + +* `bind()` + +### `unbind()` + +当字段从数据表中移除时触发的执行内容。通常用于移除数据表事件监听器和其他处理。 + +继承时需要先调用对应的 `super.unbind()` 方法。 + +**签名** + +* `unbind()` + +### `get()` + +获取字段的配置项的值。 + +**签名** + +* `get(key: string): any` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `key` | `string` | - | 配置项名称 | + +**示例** + +```ts +const field = db.collection('users').getField('name'); + +// 获取字段名称配置项的值,返回 'name' +console.log(field.get('name')); +``` + +### `merge()` + +合并字段的配置项的值。 + +**签名** + +* `merge(options: { [key: string]: any }): void` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options` | `{ [key: string]: any }` | - | 要合并的配置项对象 | + +**示例** + +```ts +const field = db.collection('users').getField('name'); + +field.merge({ + // 添加一个索引配置 + index: true +}); +``` + +### `remove()` + +从数据表中移除字段(仅从内存中移除)。 + +**示例** + +```ts +const books = db.getCollections('books'); + +books.getField('isbn').remove(); + +// really remove from db +await books.sync(); +``` + +## 数据库方法 + +### `removeFromDb()` + +从数据库中移除字段。 + +**签名** + +* `removeFromDb(options?: Transactionable): Promise` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options.transaction?` | `Transaction` | - | 事务实例 | + +### `existsInDb()` + +判断字段是否存在于数据库中。 + +**签名** + +* `existsInDb(options?: Transactionable): Promise` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options.transaction?` | `Transaction` | - | 事务实例 | + +## 内置字段类型列表 + +NocoBase 内置了一些常用的字段类型,可以直接在定义数据表的字段是使用对应的 type 名称来指定类型。不同类型的字段参数配置不同,具体可参考下面的列表。 + +所有字段类型的配置项除了以下额外介绍的以外,都会透传至 Sequelize,所以所有 Sequelize 支持的字段配置项都可以在这里使用(如 `allowNull`、`defaultValue` 等)。 + +另外 server 端的字段类型主要解决数据库存储和部分算法的问题,与前端的字段展示类型和使用组件基本无关。前端字段类型可以参考教程对应说明。 + +### `'boolean'` + +逻辑值类型。 + +**示例** + +```js +db.collection({ + name: 'books', + fields: [ + { + type: 'boolean', + name: 'published' + } + ] +}); +``` + +### `'integer'` + +整型(32 位)。 + +**示例** + +```ts +db.collection({ + name: 'books', + fields: [ + { + type: 'integer', + name: 'pages' + } + ] +}); +``` + +### `'bigInt'` + +长整型(64 位)。 + +**示例** + +```ts +db.collection({ + name: 'books', + fields: [ + { + type: 'bigInt', + name: 'words' + } + ] +}); +``` + +### `'double'` + +双精度浮点型(64 位)。 + +**示例** + +```ts +db.collection({ + name: 'books', + fields: [ + { + type: 'double', + name: 'price' + } + ] +}); +``` + +### `'real'` + +实数类型(仅 PG 适用)。 + +### `'decimal'` + +十进制小数类型。 + +### `'string'` + +字符串类型。相当于大部分数据库的 `VARCHAR` 类型。 + +**示例** + +```ts +db.collection({ + name: 'books', + fields: [ + { + type: 'string', + name: 'title' + } + ] +}); +``` + +### `'text'` + +文本类型。相当于大部分数据库的 `TEXT` 类型。 + +**示例** + +```ts +db.collection({ + name: 'books', + fields: [ + { + type: 'text', + name: 'content' + } + ] +}); +``` + +### `'password'` + +密码类型(NocoBase 扩展)。基于 Node.js 原生的 crypto 包的 `scrypt` 方法进行密码加密。 + +**示例** + +```ts +db.collection({ + name: 'users', + fields: [ + { + type: 'password', + name: 'password', + length: 64, // 长度,默认 64 + randomBytesSize: 8 // 随机字节长度,默认 8 + } + ] +}); +``` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `length` | `number` | 64 | 字符长度 | +| `randomBytesSize` | `number` | 8 | 随机字节大小 | + +### `'date'` + +日期类型。 + +### `'time'` + +时间类型。 + +### `'array'` + +数组类型(仅 PG 适用)。 + +### `'json'` + +JSON 类型。 + +### `'jsonb'` + +JSONB 类型(仅 PG 适用,其他会被兼容为 `'json'` 类型)。 + +### `'uuid'` + +UUID 类型。 + +### `'uid'` + +UID 类型(NocoBase 扩展)。短随机字符串标识符类型。 + +### `'formula'` + +公式类型(NocoBase 扩展)。可配置基于 [mathjs](https://www.npmjs.com/package/mathjs) 的数学公式计算,公式中可以引用同一条记录中其他列的数值参与计算。 + +**示例** + +```ts +db.collection({ + name: 'orders', + fields: [ + { + type: 'double', + name: 'price' + }, + { + type: 'integer', + name: 'quantity' + }, + { + type: 'formula', + name: 'total', + expression: 'price * quantity' + } + ] +}); +``` + +### `'radio'` + +单选类型(NocoBase 扩展)。全表最多有一行数据的该字段值为 `true`,其他都为 `false` 或 `null`。 + +**示例** + +整个系统只有一个被标记为 root 的用户,任意另一个用户的 root 值被改为 `true` 之后,其他所有 root 为 `true` 的记录均会被修改为 `false`: + +```ts +db.collection({ + name: 'users', + fields: [ + { + type: 'radio', + name: 'root', + } + ] +}); +``` + +### `'sort'` + +排序类型(NocoBase 扩展)。基于整型数字进行排序,为新记录自动生成新序号,当移动数据时进行序号重排。 + +数据表如果定义了 `sortable` 选项,也会自动生成对应字段。 + +**示例** + +文章基于所属用户可排序: + +```ts +db.collection({ + name: 'posts', + fields: [ + { + type: 'belongsTo', + name: 'user', + }, + { + type: 'sort', + name: 'priority', + scopeKey: 'userId' // 以 userId 相同值分组的数据进行排序 + } + ] +}); +``` + +### `'virtual'` + +虚拟类型。不实际储存数据,仅用于特殊 getter/setter 定义时使用。 + +### `'belongsTo'` + +多对一关联类型。外键储存在自身表,与 hasOne/hasMany 相对。 + +**示例** + +任意文章属于某个作者: + +```ts +db.collection({ + name: 'posts', + fields: [ + { + type: 'belongsTo', + name: 'author', + target: 'users', // 不配置默认为 name 复数名称的表名 + foreignKey: 'authorId', // 不配置默认为 + Id 的格式 + sourceKey: 'id' // 不配置默认为 target 表的 id + } + ] +}); +``` + +### `'hasOne'` + +一对一关联类型。外键储存在关联表,与 belongsTo 相对。 + +**示例** + +任意用户都有一份个人资料: + +```ts +db.collection({ + name: 'users', + fields: [ + { + type: 'hasOne', + name: 'profile', + target: 'profiles', // 可省略 + } + ] +}) +``` + +### `'hasMany'` + +一对多关联类型。外键储存在关联表,与 belongsTo 相对。 + +**示例** + +任意用户可以拥有多篇文章: + +```ts +db.collection({ + name: 'users', + fields: [ + { + type: 'hasMany', + name: 'posts', + foreignKey: 'authorId', + sourceKey: 'id' + } + ] +}); +``` + +### `'belongsToMany'` + +多对多关联类型。使用中间表储存双方外键,如不指定已存在的表为中间表的话,将会自动创建中间表。 + +**示例** + +任意文章可以加任意多个标签,任意标签也可以被任意多篇文章添加: + +```ts +db.collection({ + name: 'posts', + fields: [ + { + type: 'belongsToMany', + name: 'tags', + target: 'tags', // 同名可省略 + through: 'postsTags', // 中间表不配置将自动生成 + foreignKey: 'postId', // 自身表在中间表的外键 + sourceKey: 'id', // 自身表的主键 + otherKey: 'tagId' // 关联表在中间表的外键 + } + ] +}); + +db.collection({ + name: 'tags', + fields: [ + { + type: 'belongsToMany', + name: 'posts', + through: 'postsTags', // 同一组关系指向同一张中间表 + } + ] +}); +``` diff --git a/docs/tr-TR/api/database/index.md b/docs/tr-TR/api/database/index.md new file mode 100644 index 000000000..975fa52aa --- /dev/null +++ b/docs/tr-TR/api/database/index.md @@ -0,0 +1,1130 @@ +# Database + +NocoBase 内置的数据库访问类,通过封装 [Sequelize](https://sequelize.org/) 提供了更加简单的数据库访问接口和统一化的 JSON 数据库表配置方式,同时也提供了扩展字段类型和查询操作符的能力。 + +Database 类继承自 EventEmitter,可以通过 `db.on('event', callback)` 监听数据库事件,以及 `db.off('event', callback)` 移除监听。 + +## 包结构 + +可通过以下方式引入相关实体: + +```ts +import Database, { + Field, + Collection, + Repository, + RelationRepository, + extend +} from '@nocobase/database'; +``` + +## 构造函数 + +**签名** + +* `constructor(options: DatabaseOptions)` + +创建一个数据库实例。 + +**参数** + +`options` 参数与 [Sequelize 的构造参数](https://sequelize.org/api/v6/class/src/sequelize.js~sequelize#instance-constructor-constructor)一致的部分会透传至 Sequelize,同时 NocoBase 也会使用一些额外的参数: + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options.host` | `string` | `'localhost'` | 数据库主机 | +| `options.port` | `number` | - | 数据库服务端口,根据使用的数据库有对应默认端口 | +| `options.username` | `string` | - | 数据库用户名 | +| `options.password` | `string` | - | 数据库密码 | +| `options.database` | `string` | - | 数据库名称 | +| `options.dialect` | `string` | `'mysql'` | 数据库类型 | +| `options.storage?` | `string` | `':memory:'` | SQLite 的存储模式 | +| `options.logging?` | `boolean` | `false` | 是否开启日志 | +| `options.define?` | `Object` | `{}` | 默认的表定义参数 | +| `options.tablePrefix?` | `string` | `''` | NocoBase 扩展,表名前缀 | +| `options.migrator?` | `UmzugOptions` | `{}` | NocoBase 扩展,迁移管理器相关参数,参考 [Umzug](https://github.com/sequelize/umzug/blob/main/src/types.ts#L15) 实现 | + +**示例** + +```ts +import Database from '@nocobase/database'; + +const app = new Database({ + dialect: 'mysql', + host: 'localhost', + port: 3306, + username: 'root', + password: '123456', + database: 'test', + tablePrefix: 'my_' +}); +``` + +## 实例成员 + +### `sequelize` + +初始化后的 Sequelize 实例,在需要使用 sequelize 底层方法时可以调用,相关信息可以直接参考 sequelize 的文档。 + +### `options` + +初始化的配置参数,包含了 Sequelize 的配置参数和 NocoBase 的额外配置参数。 + +### `version` + +连接的数据库的版本信息对象,可通过 `await db.version.satisfies()` 检查是否满足特定数据库版本要求。 + +**示例** + +```ts +const r = await this.db.version.satisfies({ + mysql: '>=8.0.17', + sqlite: '3.x', + postgres: '>=10', +}); +``` + +## 迁移相关方法 + +### `addMigration()` + +添加单个迁移文件。 + +**签名** + +* `addMigration(options: MigrationItem)` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options.name` | `string` | - | 迁移文件名称 | +| `options.context?` | `string` | - | 迁移文件的 `up` 方法 | +| `options.migration?` | `typeof Migration` | - | 迁移文件的自定义类 | +| `options.up` | `Function` | - | 迁移文件的 `up` 方法 | +| `options.down` | `Function` | - | 迁移文件的 `down` 方法 | + +**示例** + +```ts +db.addMigration({ + name: '20220916120411-test-1', + async up() { + const queryInterface = this.context.db.sequelize.getQueryInterface(); + await queryInterface.query(/* your migration sqls */); + } +}); +``` + +### `addMigrations()` + +添加指定目录下的迁移文件。 + +**签名** + +* `addMigrations(options: AddMigrationsOptions): void` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options.directory` | `string` | `''` | 迁移文件所在目录 | +| `options.extensions` | `string[]` | `['js', 'ts']` | 文件扩展名 | +| `options.namespace?` | `string` | `''` | 命名空间 | +| `options.context?` | `Object` | `{ db }` | 迁移文件的上下文 | + +**示例** + +```ts +db.addMigrations({ + directory: path.resolve(__dirname, './migrations'), + namespace: 'test' +}); +``` + +## 工具方法 + +### `inDialect()` + +判断当前数据库类型是否为指定类型。 + +**签名** + +* `inDialect(dialect: string[]): boolean` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `dialect` | `string[]` | - | 数据库类型,可选值为 `mysql`/`postgres`/`sqlite` | + +### `getTablePrefix()` + +获取配置中的表名前缀。 + +**签名** + +* `getTablePrefix(): string` + +## 数据表配置 + +### `collection()` + +定义一个数据表。该调用类似与 Sequelize 的 `define` 方法,只在内存中创建表结构,如需持久化到数据库,需要调用 `sync` 方法。 + +**签名** + +* `collection(options: CollectionOptions): Collection` + +**参数** + +`options` 所有配置参数与 `Collection` 类的构造函数一致,参考 [Collection](/api/server/database/collection#构造函数)。 + +**事件** + +* `'beforeDefineCollection'`:在定义表之前触发。 +* `'afterDefineCollection'`:在定义表之后触发。 + +**示例** + +```ts +db.collection({ + name: 'books', + fields: [ + { + type: 'string', + name: 'title', + }, + { + type: 'float', + name: 'price', + } + ] +}); + +// sync collection as table to db +await db.sync(); +``` + +### `getCollection()` + +获取已定义的数据表。 + +**签名** + +* `getCollection(name: string): Collection` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `name` | `string` | - | 表名 | + +**示例** + +```ts +const collection = db.getCollection('books'); +``` + +### `hasCollection()` + +判断是否已定义指定的数据表。 + +**签名** + +* `hasCollection(name: string): boolean` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `name` | `string` | - | 表名 | + +**示例** + +```ts +db.collection({ name: 'books' }); + +db.hasCollection('books'); // true + +db.hasCollection('authors'); // false +``` + +### `removeCollection()` + +移除已定义的数据表。仅在内存中移除,如需持久化,需要调用 `sync` 方法。 + +**签名** + +* `removeCollection(name: string): void` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `name` | `string` | - | 表名 | + +**事件** + +* `'beforeRemoveCollection'`:在移除表之前触发。 +* `'afterRemoveCollection'`:在移除表之后触发。 + +**示例** + +```ts +db.collection({ name: 'books' }); + +db.removeCollection('books'); +``` + +### `import()` + +导入文件目录下所有文件作为 collection 配置载入内存。 + +**签名** + +* `async import(options: { directory: string; extensions?: ImportFileExtension[] }): Promise>` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options.directory` | `string` | - | 要导入的目录路径 | +| `options.extensions` | `string[]` | `['ts', 'js']` | 扫描特定后缀 | + +**示例** + +`./collections/books.ts` 文件定义的 collection 如下: + +```ts +export default { + name: 'books', + fields: [ + { + type: 'string', + name: 'title', + } + ] +}; +``` + +在插件加载时导入相关配置: + +```ts +class Plugin { + async load() { + await this.app.db.import({ + directory: path.resolve(__dirname, './collections'), + }); + } +} +``` + +## 扩展注册与获取 + +### `registerFieldTypes()` + +注册自定义字段类型。 + +**签名** + +* `registerFieldTypes(fieldTypes: MapOf): void` + +**参数** + +`fieldTypes` 是一个键值对,键为字段类型名称,值为字段类型类。 + +**示例** + +```ts +import { Field } from '@nocobase/database'; + +class MyField extends Field { + // ... +} + +db.registerFieldTypes({ + myField: MyField, +}); +``` + +### `registerModels()` + +注册自定义数据模型类。 + +**签名** + +* `registerModels(models: MapOf>): void` + +**参数** + +`models` 是一个键值对,键为数据模型名称,值为数据模型类。 + +**示例** + +```ts +import { Model } from '@nocobase/database'; + +class MyModel extends Model { + // ... +} + +db.registerModels({ + myModel: MyModel, +}); + +db.collection({ + name: 'myCollection', + model: 'myModel' +}); +``` + +### `registerRepositories()` + +注册自定义数据仓库类。 + +**签名** + +* `registerRepositories(repositories: MapOf): void` + +**参数** + +`repositories` 是一个键值对,键为数据仓库名称,值为数据仓库类。 + +**示例** + +```ts +import { Repository } from '@nocobase/database'; + +class MyRepository extends Repository { + // ... +} + +db.registerRepositories({ + myRepository: MyRepository, +}); + +db.collection({ + name: 'myCollection', + repository: 'myRepository' +}); +``` + +### `registerOperators()` + +注册自定义数据查询操作符。 + +**签名** + +* `registerOperators(operators: MapOf)` + +**参数** + +`operators` 是一个键值对,键为操作符名称,值为操作符比较语句生成函数。 + +**示例** + +```ts +db.registerOperators({ + $dateOn(value) { + return { + [Op.and]: [{ [Op.gte]: stringToDate(value) }, { [Op.lt]: getNextDay(value) }], + }; + } +}); + +db.getRepository('books').count({ + filter: { + createdAt: { + // registered operator + $dateOn: '2020-01-01', + } + } +}); +``` + +### `getModel()` + +获取已定义的数据模型类。如果没有在之前注册自定义模型类,将返回 Sequelize 默认的模型类。默认名称与 collection 定义的名称相同。 + +**签名** + +* `getModel(name: string): Model` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `name` | `string` | - | 已注册的模型名 | + +**示例** + +```ts +db.registerModels({ + books: class MyModel extends Model {} +}); + +const ModelClass = db.getModel('books'); + +console.log(ModelClass.prototype instanceof MyModel) // true +``` + +注:从 collection 中获取的模型类并不与注册时的模型类严格相等,而是继承自注册时的模型类。由于 Sequelize 的模型类在初始化过程中属性会被修改,所以 NocoBase 自动处理了这个继承关系。除类不相等以外,其他所有定义都可以正常使用。 + +### `getRepository()` + +获取自定义的数据仓库类。如果没有在之前注册自定义数据仓库类,将返回 NocoBase 默认的数据仓库类。默认名称与 collection 定义的名称相同。 + +数据仓库类主要用于基于数据模型的增删改查等操作,参考 [数据仓库](/api/server/database/repository)。 + +**签名** + +* `getRepository(name: string): Repository` +* `getRepository(name: string, relationId?: string | number): Repository` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `name` | `string` | - | 已注册的数据仓库名 | +| `relationId` | `string` \| `number` | - | 关系数据的外键值 | + +当名称是形如 `'tables.relactions'` 的带关联的名称时,将返回关联的数据仓库类。如果提供了第二个参数,数据仓库在使用时(查询、修改等)会基于关系数据的外键值。 + +**示例** + +假设有两张数据表_文章_与_作者_,并且文章表中有一个外键指向作者表: + +```ts +const AuthorsRepo = db.getRepository('authors'); +const author1 = AuthorsRepo.create({ name: 'author1' }); + +const PostsRepo = db.getRepository('authors.posts', author1.id); +const post1 = AuthorsRepo.create({ title: 'post1' }); +asset(post1.authorId === author1.id); // true +``` + +## 数据库事件 + +### `on()` + +监听数据库事件。 + +**签名** + +* `on(event: string, listener: (...args: any[]) => void | Promise): void` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| event | string | - | 事件名称 | +| listener | Function | - | 事件监听器 | + +事件名称默认支持 Sequelize 的 Model 事件。针对全局事件,通过 `` 的名称方式监听,针对单 Model 事件,通过 `.` 的名称方式监听。 + +所有内置的事件类型的参数说明和详细示例参考 [内置事件](#内置事件) 部分内容。 + +### `off()` + +移除事件监听函数。 + +**签名** + +* `off(name: string, listener: Function)` + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| name | string | - | 事件名称 | +| listener | Function | - | 事件监听器 | + +**示例** + +```ts +const listener = async (model, options) => { + console.log(model); +}; + +db.on('afterCreate', listener); + +db.off('afterCreate', listener); +``` + +## 数据库操作 + +### `auth()` + +数据库连接验证。可以用于确保应用与数据已建立连接。 + +**签名** + +* `auth(options: QueryOptions & { retry?: number } = {}): Promise` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options?` | `Object` | - | 验证选项 | +| `options.retry?` | `number` | `10` | 验证失败时重试次数 | +| `options.transaction?` | `Transaction` | - | 事务对象 | +| `options.logging?` | `boolean \| Function` | `false` | 是否打印日志 | + +**示例** + +```ts +await db.auth(); +``` + +### `reconnect()` + +重新连接数据库。 + +**示例** + +```ts +await db.reconnect(); +``` + +### `closed()` + +判断数据库是否已关闭连接。 + +**签名** + +* `closed(): boolean` + +### `close()` + +关闭数据库连接。等同于 `sequelize.close()`。 + +### `sync()` + +同步数据库表结构。等同于 `sequelize.sync()`,参数参考 [Sequelize 文档](https://sequelize.org/api/v6/class/src/sequelize.js~sequelize#instance-method-sync)。 + +### `clean()` + +清空数据库,将删除所有数据表。 + +**签名** + +* `clean(options: CleanOptions): Promise` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options.drop` | `boolean` | `false` | 是否移除所有数据表 | +| `options.skip` | `string[]` | - | 跳过的表名配置 | +| `options.transaction` | `Transaction` | - | 事务对象 | + +**示例** + +移除除 `users` 表以外的所有表。 + +```ts +await db.clean({ + drop: true, + skip: ['users'] +}) +``` + +## 包级导出 + +### `defineCollection()` + +创建一个数据表的配置内容。 + +**签名** + +* `defineCollection(name: string, config: CollectionOptions): CollectionOptions` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `collectionOptions` | `CollectionOptions` | - | 与所有 `db.collection()` 的参数相同 | + +**示例** + +对于要被 `db.import()` 导入的数据表配置文件: + +```ts +import { defineCollection } from '@nocobase/database'; + +export default defineCollection({ + name: 'users', + fields: [ + { + type: 'string', + name: 'name', + }, + ], +}); +``` + +### `extendCollection()` + +扩展已在内存中的表结构配置内容,主要用于 `import()` 方法导入的文件内容。该方法是 `@nocobase/database` 包导出的顶级方法,不通过 db 实例调用。也可以使用 `extend` 别名。 + +**签名** + +* `extendCollection(collectionOptions: CollectionOptions, mergeOptions?: MergeOptions): ExtendedCollectionOptions` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `collectionOptions` | `CollectionOptions` | - | 与所有 `db.collection()` 的参数相同 | +| `mergeOptions?` | `MergeOptions` | - | npm 包 [deepmerge](https://npmjs.com/package/deepmerge) 的参数 | + +**示例** + +原始 books 表定义(books.ts): + +```ts +export default { + name: 'books', + fields: [ + { name: 'title', type: 'string' } + ] +} +``` + +扩展 books 表定义(books.extend.ts): + +```ts +import { extend } from '@nocobase/database'; + +// 再次扩展 +export default extend({ + name: 'books', + fields: [ + { name: 'price', type: 'number' } + ] +}); +``` + +以上两个文件如在调用 `import()` 时导入,通过 `extend()` 再次扩展以后,books 表将拥有 `title` 和 `price` 两个字段。 + +此方法在扩展已有插件已定义的表结构时非常有用。 + +## 内置事件 + +数据库会在相应的生命周期触发以下对应的事件,通过 `on()` 方法订阅后进行特定的处理可满足一些业务需要。 + +### `'beforeSync'` / `'afterSync'` + +当新的表结构配置(字段、索引等)被同步到数据库前后触发,通常在执行 `collection.sync()`(内部调用)时会触发,一般用于一些特殊的字段扩展的逻辑处理。 + +**签名** + +```ts +on(eventName: `${string}.beforeSync` | 'beforeSync' | `${string}.afterSync` | 'afterSync', listener: SyncListener): this +``` + +**类型** + +```ts +import type { SyncOptions, HookReturn } from 'sequelize/types'; + +type SyncListener = (options?: SyncOptions) => HookReturn; +``` + +**示例** + +```ts +const users = db.collection({ + name: 'users', + fields: [ + { type: 'string', name: 'username' } + ] +}); + +db.on('beforeSync', async (options) => { + // do something +}); + +db.on('users.afterSync', async (options) => { + // do something +}); + +await users.sync(); +``` + +### `'beforeValidate'` / `'afterValidate'` + +创建或更新数据前会有基于 collection 定义的规则对数据的验证过程,在验证前后会触发对应事件。当调用 `repository.create()` 或 `repository.update()` 时会触发。 + +**签名** + +```ts +on(eventName: `${string}.beforeValidate` | 'beforeValidate' | `${string}.afterValidate` | 'afterValidate', listener: ValidateListener): this +``` + +**类型** + +```ts +import type { ValidationOptions } from 'sequelize/types/lib/instance-validator'; +import type { HookReturn } from 'sequelize/types'; +import type { Model } from '@nocobase/database'; + +type ValidateListener = (model: Model, options?: ValidationOptions) => HookReturn; +``` + +**示例** + +```ts +db.collection({ + name: 'tests', + fields: [ + { + type: 'string', + name: 'email', + validate: { + isEmail: true, + }, + } + ], +}); + +// all models +db.on('beforeValidate', async (model, options) => { + // do something +}); +// tests model +db.on('tests.beforeValidate', async (model, options) => { + // do something +}); + +// all models +db.on('afterValidate', async (model, options) => { + // do something +}); +// tests model +db.on('tests.afterValidate', async (model, options) => { + // do something +}); + +const repository = db.getRepository('tests'); +await repository.create({ + values: { + email: 'abc', // checks for email format + }, +}); +// or +await repository.update({ + filterByTk: 1, + values: { + email: 'abc', // checks for email format + }, +}); +``` + +### `'beforeCreate'` / `'afterCreate'` + +创建一条数据前后会触发对应事件,当调用 `repository.create()` 时会触发。 + +**签名** + +```ts +on(eventName: `${string}.beforeCreate` | 'beforeCreate' | `${string}.afterCreate` | 'afterCreate', listener: CreateListener): this +``` + +**类型** + +```ts +import type { CreateOptions, HookReturn } from 'sequelize/types'; +import type { Model } from '@nocobase/database'; + +export type CreateListener = (model: Model, options?: CreateOptions) => HookReturn; +``` + +**示例** + +```ts +db.on('beforeCreate', async (model, options) => { + // do something +}); + +db.on('books.afterCreate', async (model, options) => { + const { transaction } = options; + const result = await model.constructor.findByPk(model.id, { + transaction + }); + console.log(result); +}); +``` + +### `'beforeUpdate'` / `'afterUpdate'` + +更新一条数据前后会触发对应事件,当调用 `repository.update()` 时会触发。 + +**签名** + +```ts +on(eventName: `${string}.beforeUpdate` | 'beforeUpdate' | `${string}.afterUpdate` | 'afterUpdate', listener: UpdateListener): this +``` + +**类型** + +```ts +import type { UpdateOptions, HookReturn } from 'sequelize/types'; +import type { Model } from '@nocobase/database'; + +export type UpdateListener = (model: Model, options?: UpdateOptions) => HookReturn; +``` + +**示例** + +```ts +db.on('beforeUpdate', async (model, options) => { + // do something +}); + +db.on('books.afterUpdate', async (model, options) => { + // do something +}); +``` + +### `'beforeSave'` / `'afterSave'` + +创建或更新一条数据前后会触发对应事件,当调用 `repository.create()` 或 `repository.update()` 时会触发。 + +**签名** + +```ts +on(eventName: `${string}.beforeSave` | 'beforeSave' | `${string}.afterSave` | 'afterSave', listener: SaveListener): this +``` + +**类型** + +```ts +import type { SaveOptions, HookReturn } from 'sequelize/types'; +import type { Model } from '@nocobase/database'; + +export type SaveListener = (model: Model, options?: SaveOptions) => HookReturn; +``` + +**示例** + +```ts +db.on('beforeSave', async (model, options) => { + // do something +}); + +db.on('books.afterSave', async (model, options) => { + // do something +}); +``` + +### `'beforeDestroy'` / `'afterDestroy'` + +删除一条数据前后会触发对应事件,当调用 `repository.destroy()` 时会触发。 + +**签名** + +```ts +on(eventName: `${string}.beforeDestroy` | 'beforeDestroy' | `${string}.afterDestroy` | 'afterDestroy', listener: DestroyListener): this +``` + +**类型** + +```ts +import type { DestroyOptions, HookReturn } from 'sequelize/types'; +import type { Model } from '@nocobase/database'; + +export type DestroyListener = (model: Model, options?: DestroyOptions) => HookReturn; +``` + +**示例** + +```ts +db.on('beforeDestroy', async (model, options) => { + // do something +}); + +db.on('books.afterDestroy', async (model, options) => { + // do something +}); +``` + +### `'afterCreateWithAssociations'` + +创建一条携带层级关系数据的数据之后会触发对应事件,当调用 `repository.create()` 时会触发。 + +**签名** + +```ts +on(eventName: `${string}.afterCreateWithAssociations` | 'afterCreateWithAssociations', listener: CreateWithAssociationsListener): this +``` + +**类型** + +```ts +import type { CreateOptions, HookReturn } from 'sequelize/types'; +import type { Model } from '@nocobase/database'; + +export type CreateWithAssociationsListener = (model: Model, options?: CreateOptions) => HookReturn; +``` + +**示例** + +```ts +db.on('afterCreateWithAssociations', async (model, options) => { + // do something +}); + +db.on('books.afterCreateWithAssociations', async (model, options) => { + // do something +}); +``` + +### `'afterUpdateWithAssociations'` + +更新一条携带层级关系数据的数据之后会触发对应事件,当调用 `repository.update()` 时会触发。 + +**签名** + +```ts +on(eventName: `${string}.afterUpdateWithAssociations` | 'afterUpdateWithAssociations', listener: CreateWithAssociationsListener): this +``` + +**类型** + +```ts +import type { UpdateOptions, HookReturn } from 'sequelize/types'; +import type { Model } from '@nocobase/database'; + +export type UpdateWithAssociationsListener = (model: Model, options?: UpdateOptions) => HookReturn; +``` + +**示例** + +```ts +db.on('afterUpdateWithAssociations', async (model, options) => { + // do something +}); + +db.on('books.afterUpdateWithAssociations', async (model, options) => { + // do something +}); +``` + +### `'afterSaveWithAssociations'` + +创建或更新一条携带层级关系数据的数据之后会触发对应事件,当调用 `repository.create()` 或 `repository.update()` 时会触发。 + +**签名** + +```ts +on(eventName: `${string}.afterSaveWithAssociations` | 'afterSaveWithAssociations', listener: SaveWithAssociationsListener): this +``` + +**类型** + +```ts +import type { SaveOptions, HookReturn } from 'sequelize/types'; +import type { Model } from '@nocobase/database'; + +export type SaveWithAssociationsListener = (model: Model, options?: SaveOptions) => HookReturn; +``` + +**示例** + +```ts +db.on('afterSaveWithAssociations', async (model, options) => { + // do something +}); + +db.on('books.afterSaveWithAssociations', async (model, options) => { + // do something +}); +``` + +### `'beforeDefineCollection'` + +当定义一个数据表之前触发,如调用 `db.collection()` 时。 + +注:该事件是同步事件。 + +**签名** + +```ts +on(eventName: 'beforeDefineCollection', listener: BeforeDefineCollectionListener): this +``` + +**类型** + +```ts +import type { CollectionOptions } from '@nocobase/database'; + +export type BeforeDefineCollectionListener = (options: CollectionOptions) => void; +``` + +**示例** + +```ts +db.on('beforeDefineCollection', (options) => { + // do something +}); +``` + +### `'afterDefineCollection'` + +当定义一个数据表之后触发,如调用 `db.collection()` 时。 + +注:该事件是同步事件。 + +**签名** + +```ts +on(eventName: 'afterDefineCollection', listener: AfterDefineCollectionListener): this +``` + +**类型** + +```ts +import type { Collection } from '@nocobase/database'; + +export type AfterDefineCollectionListener = (options: Collection) => void; +``` + +**示例** + +```ts +db.on('afterDefineCollection', (collection) => { + // do something +}); +``` + +### `'beforeRemoveCollection'` / `'afterRemoveCollection'` + +当从内存中移除一个数据表前后触发,如调用 `db.removeCollection()` 时。 + +注:该事件是同步事件。 + +**签名** + +```ts +on(eventName: 'beforeRemoveCollection' | 'afterRemoveCollection', listener: RemoveCollectionListener): this +``` + +**类型** + +```ts +import type { Collection } from '@nocobase/database'; + +export type RemoveCollectionListener = (options: Collection) => void; +``` + +**示例** + +```ts +db.on('beforeRemoveCollection', (collection) => { + // do something +}); + +db.on('afterRemoveCollection', (collection) => { + // do something +}); +``` diff --git a/docs/tr-TR/api/database/operators.md b/docs/tr-TR/api/database/operators.md new file mode 100644 index 000000000..468d5f0e4 --- /dev/null +++ b/docs/tr-TR/api/database/operators.md @@ -0,0 +1,816 @@ +# Filter Operators + +用于 Repository 的 find、findOne、findAndCount、count 等 API 的 filter 参数里。如: + +```ts +const repository = db.getRepository('books'); + +repository.find({ + filter: { + title: { + $eq: '春秋', + } + } +}); +``` + +相当于 Sequelize Where 查询的 [Op](https://sequelize.org/docs/v6/core-concepts/model-querying-basics/#operators) 对象。 + +为了支持 JSON 化,NocoBase 中将查询运算符转换为以 $ 为前缀的字符串标识。 + +另外,NocoBase 也提供了扩展运算符的 API,详见 [`db.registerOperators()`](../database#registeroperators)。 + +## 通用运算符 + +### `$eq` + +判断字段值是否相等于指定值。相当于 SQL 的 `=`。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $eq: '春秋', + } + } +}); +``` + +等同于 `title: '春秋'`。 + +### `$ne` + +判断字段值是否不等于指定值。相当于 SQL 的 `!=`。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $ne: '春秋', + } + } +}); +``` + +### `$is` + +判断字段值是否为指定值。相当于 SQL 的 `IS`。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $is: null, + } + } +}); +``` + +### `$not` + +判断字段值是否不为指定值。相当于 SQL 的 `IS NOT`。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $not: null, + } + } +}); +``` + +### `$col` + +判断字段值是否等于另一个字段的值。相当于 SQL 的 `=`。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $col: 'name', + } + } +}); +``` + +### `$in` + +判断字段值是否在指定数组中。相当于 SQL 的 `IN`。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $in: ['春秋', '战国'], + } + } +}); +``` + +### `$notIn` + +判断字段值是否不在指定数组中。相当于 SQL 的 `NOT IN`。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $notIn: ['春秋', '战国'], + } + } +}); +``` + +### `$empty` + +判断一般字段是否为空,如果是字符串字段,判断是否为空串,如果是数组字段,判断是否为空数组。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $empty: true, + } + } +}); +``` + +### `$notEmpty` + +判断一般字段是否不为空,如果是字符串字段,判断是否不为空串,如果是数组字段,判断是否不为空数组。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $notEmpty: true, + } + } +}); +``` + +## 逻辑运算符 + +### `$and` + +逻辑 AND。相当于 SQL 的 `AND`。 + +**示例** + +```ts +repository.find({ + filter: { + $and: [ + { title: '诗经' }, + { isbn: '1234567890' }, + ] + } +}); +``` + +### `$or` + +逻辑 OR。相当于 SQL 的 `OR`。 + +**示例** + +```ts +repository.find({ + filter: { + $or: [ + { title: '诗经' }, + { publishedAt: { $lt: '0000-00-00T00:00:00Z' } }, + ] + } +}); +``` + +## 布尔类型字段运算符 + +用于布尔类型字段 `type: 'boolean'` + +### `$isFalsy` + +判断布尔类型字段值是否为假。布尔字段值为 `false`、`0` 和 `NULL` 的情况都会被判断为 `$isFalsy: true`。 + +**示例** + +```ts +repository.find({ + filter: { + isPublished: { + $isFalsy: true, + } + } +}) +``` + +### `$isTruly` + +判断布尔类型字段值是否为真。布尔字段值为 `true` 和 `1` 的情况都会被判断为 `$isTruly: true`。 + +**示例** + +```ts +repository.find({ + filter: { + isPublished: { + $isTruly: true, + } + } +}) +``` + +## 数字类型字段运算符 + +用于数字类型字段,包括: + +- `type: 'integer'` +- `type: 'float'` +- `type: 'double'` +- `type: 'real'` +- `type: 'decimal'` + +### `$gt` + +判断字段值是否大于指定值。相当于 SQL 的 `>`。 + +**示例** + +```ts +repository.find({ + filter: { + price: { + $gt: 100, + } + } +}); +``` + +### `$gte` + +判断字段值是否大于等于指定值。相当于 SQL 的 `>=`。 + +**示例** + +```ts +repository.find({ + filter: { + price: { + $gte: 100, + } + } +}); +``` + +### `$lt` + +判断字段值是否小于指定值。相当于 SQL 的 `<`。 + +**示例** + +```ts +repository.find({ + filter: { + price: { + $lt: 100, + } + } +}); +``` + +### `$lte` + +判断字段值是否小于等于指定值。相当于 SQL 的 `<=`。 + +**示例** + +```ts +repository.find({ + filter: { + price: { + $lte: 100, + } + } +}); +``` + +### `$between` + +判断字段值是否在指定的两个值之间。相当于 SQL 的 `BETWEEN`。 + +**示例** + +```ts +repository.find({ + filter: { + price: { + $between: [100, 200], + } + } +}); +``` + +### `$notBetween` + +判断字段值是否不在指定的两个值之间。相当于 SQL 的 `NOT BETWEEN`。 + +**示例** + +```ts +repository.find({ + filter: { + price: { + $notBetween: [100, 200], + } + } +}); +``` + +## 字符串类型字段运算符 + +用于字符串类型字段,包括 `string` + +### `$includes` + +判断字符串字段是否包含指定子串。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $includes: '三字经', + } + } +}) +``` + +### `$notIncludes` + +判断字符串字段是否不包含指定子串。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $notIncludes: '三字经', + } + } +}) +``` + +### `$startsWith` + +判断字符串字段是否以指定子串开头。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $startsWith: '三字经', + } + } +}) +``` + +### `$notStatsWith` + +判断字符串字段是否不以指定子串开头。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $notStatsWith: '三字经', + } + } +}) +``` + +### `$endsWith` + +判断字符串字段是否以指定子串结尾。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $endsWith: '三字经', + } + } +}) +``` + +### `$notEndsWith` + +判断字符串字段是否不以指定子串结尾。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $notEndsWith: '三字经', + } + } +}) +``` + +### `$like` + +判断字段值是否包含指定的字符串。相当于 SQL 的 `LIKE`。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $like: '计算机', + } + } +}); +``` + +### `$notLike` + +判断字段值是否不包含指定的字符串。相当于 SQL 的 `NOT LIKE`。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $notLike: '计算机', + } + } +}); +``` + +### `$iLike` + +判断字段值是否包含指定的字符串,忽略大小写。相当于 SQL 的 `ILIKE`(仅 PG 适用)。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $iLike: 'Computer', + } + } +}); +``` + +### `$notILike` + +判断字段值是否不包含指定的字符串,忽略大小写。相当于 SQL 的 `NOT ILIKE`(仅 PG 适用)。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $notILike: 'Computer', + } + } +}); +``` + +### `$regexp` + +判断字段值是否匹配指定的正则表达式。相当于 SQL 的 `REGEXP`(仅 PG 适用)。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $regexp: '^计算机', + } + } +}); +``` + +### `$notRegexp` + +判断字段值是否不匹配指定的正则表达式。相当于 SQL 的 `NOT REGEXP`(仅 PG 适用)。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $notRegexp: '^计算机', + } + } +}); +``` + +### `$iRegexp` + +判断字段值是否匹配指定的正则表达式,忽略大小写。相当于 SQL 的 `~*`(仅 PG 适用)。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $iRegexp: '^COMPUTER', + } + } +}); +``` + +### `$notIRegexp` + +判断字段值是否不匹配指定的正则表达式,忽略大小写。相当于 SQL 的 `!~*`(仅 PG 适用)。 + +**示例** + +```ts +repository.find({ + filter: { + title: { + $notIRegexp: '^COMPUTER', + } + } +}); +``` + +## 日期类型字段运算符 + +用于日期类型字段 `type: 'date'` + +### `$dateOn` + +判断日期字段是否在某天内。 + +**示例** + +```ts +repository.find({ + filter: { + createdAt: { + $dateOn: '2021-01-01', + } + } +}) +``` + +### `$dateNotOn` + +判断日期字段是否不在某天内。 + +**示例** + +```ts +repository.find({ + filter: { + createdAt: { + $dateNotOn: '2021-01-01', + } + } +}) +``` + +### `$dateBefore` + +判断日期字段是否在某个值之前。相当于小于传入的日期值。 + +**示例** + +```ts +repository.find({ + filter: { + createdAt: { + $dateBefore: '2021-01-01T00:00:00.000Z', + } + } +}) +``` + +### `$dateNotBefore` + +判断日期字段是否不在某个值之前。相当于大于等于传入的日期值。 + +**示例** + +```ts +repository.find({ + filter: { + createdAt: { + $dateNotBefore: '2021-01-01T00:00:00.000Z', + } + } +}) +``` + +### `$dateAfter` + +判断日期字段是否在某个值之后。相当于大于传入的日期值。 + +**示例** + +```ts +repository.find({ + filter: { + createdAt: { + $dateAfter: '2021-01-01T00:00:00.000Z', + } + } +}) +``` + +### `$dateNotAfter` + +判断日期字段是否不在某个值之后。相当于小于等于传入的日期值。 + +**示例** + +```ts +repository.find({ + filter: { + createdAt: { + $dateNotAfter: '2021-01-01T00:00:00.000Z', + } + } +}) +``` + +## 数组类型字段运算符 + +用于数组类型字段 `type: 'array'` + +### `$match` + +判断数组字段的值是否匹配指定数组中的值。 + +**示例** + +```ts +repository.find({ + filter: { + tags: { + $match: ['文学', '历史'], + } + } +}) +``` + +### `$notMatch` + +判断数组字段的值是否不匹配指定数组中的值。 + +**示例** + +```ts +repository.find({ + filter: { + tags: { + $notMatch: ['文学', '历史'], + } + } +}) +``` + +### `$anyOf` + +判断数组字段的值是否包含指定数组中的任意值。 + +**示例** + +```ts +repository.find({ + filter: { + tags: { + $anyOf: ['文学', '历史'], + } + } +}) +``` + +### `$noneOf` + +判断数组字段的值是否不包含指定数组中的任意值。 + +**示例** + +```ts +repository.find({ + filter: { + tags: { + $noneOf: ['文学', '历史'], + } + } +}) +``` + +### `$arrayEmpty` + +判断数组字段是否为空。 + +**示例** + +```ts +repository.find({ + filter: { + tags: { + $arrayEmpty: true, + } + } +}); +``` + +### `$arrayNotEmpty` + +判断数组字段是否不为空。 + +**示例** + +```ts +repository.find({ + filter: { + tags: { + $arrayNotEmpty: true, + } + } +}); +``` + +## 关系字段类型运算符 + +用于判断关系是否存在,字段类型包括: + +- `type: 'hasOne'` +- `type: 'hasMany'` +- `type: 'belongsTo'` +- `type: 'belongsToMany'` + +### `$exists` + +有关系数据 + +**示例** + +```ts +repository.find({ + filter: { + author: { + $exists: true, + } + } +}); +``` + +### `$notExists` + +无关系数据 + +**示例** + +```ts +repository.find({ + filter: { + author: { + $notExists: true, + } + } +}); +``` diff --git a/docs/tr-TR/api/database/relation-repository/belongs-to-many-repository.md b/docs/tr-TR/api/database/relation-repository/belongs-to-many-repository.md new file mode 100644 index 000000000..1c612ed46 --- /dev/null +++ b/docs/tr-TR/api/database/relation-repository/belongs-to-many-repository.md @@ -0,0 +1,181 @@ +# BelongsToManyRepository +`BelongsToManyRepository` 是用于处理 `BelongsToMany` 关系的 `Relation Repository`。 + +不同于其他关系类型,`BelongsToMany` 类型的关系需要通过中间表来记录。 +在 `Nocobase` 中定义关联关系,可自动创建中间表,也可以明确指定中间表。 + +## 类方法 + +### `find()` + +查找关联对象 + +**签名** + +* `async find(options?: FindOptions): Promise` + +**详细信息** + +查询参数与 [`Repository.find()`](../repository.md#find) 一致。 + +### `findOne()` + +查找关联对象,仅返回一条记录 + +**签名** + +* `async findOne(options?: FindOneOptions): Promise` + + + + +### `count()` + +返回符合查询条件的记录数 + +**签名** + +* `async count(options?: CountOptions)` + +**类型** +```typescript +interface CountOptions extends Omit, Transactionable { + filter?: Filter; +} +``` + +### `findAndCount()` + +从数据库查询特定条件的数据集和结果数。 + +**签名** + +* `async findAndCount(options?: FindAndCountOptions): Promise<[any[], number]>` + +**类型** +```typescript +type FindAndCountOptions = CommonFindOptions +``` + +### `create()` + +创建关联对象 + +**签名** + +* `async create(options?: CreateOptions): Promise` + + + +### `update()` + +更新符合条件的关联对象 + +**签名** + +* `async update(options?: UpdateOptions): Promise` + + + +### `destroy()` + +删除符合条件的关联对象 + +**签名** + +* `async destroy(options?: TargetKey | TargetKey[] | DestroyOptions): Promise` + + + +### `add()` + +添加新的关联对象 + +**签名** + +* `async add( + options: TargetKey | TargetKey[] | PrimaryKeyWithThroughValues | PrimaryKeyWithThroughValues[] | AssociatedOptions + ): Promise` + +**类型** + +```typescript +type PrimaryKeyWithThroughValues = [TargetKey, Values]; + +interface AssociatedOptions extends Transactionable { + tk?: TargetKey | TargetKey[] | PrimaryKeyWithThroughValues | PrimaryKeyWithThroughValues[]; +} +``` + +**详细信息** + +可以直接传入关联对象的 `targetKey`,也可将 `targetKey` 与中间表的字段值一并传入。 + +**示例** +```typescript +const t1 = await Tag.repository.create({ + values: { name: 't1' }, +}); + +const t2 = await Tag.repository.create({ + values: { name: 't2' }, +}); + +const p1 = await Post.repository.create({ + values: { title: 'p1' }, +}); + +const PostTagRepository = new BelongsToManyRepository(Post, 'tags', p1.id); + +// 传入 targetKey +PostTagRepository.add([ + t1.id, t2.id +]); + +// 传入中间表字段 +PostTagRepository.add([ + [t1.id, { tagged_at: '123' }], + [t2.id, { tagged_at: '456' }], +]); +``` + +### `set()` + +设置关联对象 + +**签名** +* async set( + options: TargetKey | TargetKey[] | PrimaryKeyWithThroughValues | PrimaryKeyWithThroughValues[] | AssociatedOptions, + ): Promise + +**详细信息** + +参数同 [add()](#add) + +### `remove()` + +移除与给定对象之间的关联关系 + +**签名** +* `async remove(options: TargetKey | TargetKey[] | AssociatedOptions)` + +**类型** +```typescript +interface AssociatedOptions extends Transactionable { + tk?: TargetKey | TargetKey[]; +} +``` + +### `toggle()` + +切换关联对象。 + +在一些业务场景中,经常需要切换关联对象,比如用户收藏商品,用户可以取消收藏,也可以再次收藏。使用 `toggle` 方法可以快速实现类似功能。 + +**签名** + +* `async toggle(options: TargetKey | { tk?: TargetKey; transaction?: Transaction }): Promise` + +**详细信息** + +`toggle` 方法会自动判断关联对象是否已经存在,如果存在则移除,如果不存在则添加。 diff --git a/docs/tr-TR/api/database/relation-repository/belongs-to-repository.md b/docs/tr-TR/api/database/relation-repository/belongs-to-repository.md new file mode 100644 index 000000000..15824a3d2 --- /dev/null +++ b/docs/tr-TR/api/database/relation-repository/belongs-to-repository.md @@ -0,0 +1,3 @@ +## BelongsToRepository + +`BelongsToRepository` 是用于处理 `BelongsTo` 关系的 `Repository`,它提供了一些便捷的方法来处理 `BelongsTo` 关系。其接口与 [HasOneRepository](./has-one-repository.md) 一致。 diff --git a/docs/tr-TR/api/database/relation-repository/has-many-repository.md b/docs/tr-TR/api/database/relation-repository/has-many-repository.md new file mode 100644 index 000000000..2e4b2be42 --- /dev/null +++ b/docs/tr-TR/api/database/relation-repository/has-many-repository.md @@ -0,0 +1,132 @@ + +# HasManyRepository + +`HasManyRepository` 是用于处理 `HasMany` 关系的 `Relation Repository`。 + +## 类方法 + +### `find()` + +查找关联对象 + +**签名** + +* `async find(options?: FindOptions): Promise` + +**详细信息** + +查询参数与 [`Repository.find()`](../repository.md#find) 一致。 + +### `findOne()` + +查找关联对象,仅返回一条记录 + +**签名** + +* `async findOne(options?: FindOneOptions): Promise` + + + + +### `count()` + +返回符合查询条件的记录数 + +**签名** + +* `async count(options?: CountOptions)` + +**类型** +```typescript +interface CountOptions extends Omit, Transactionable { + filter?: Filter; +} +``` + +### `findAndCount()` + +从数据库查询特定条件的数据集和结果数。 + +**签名** + +* `async findAndCount(options?: FindAndCountOptions): Promise<[any[], number]>` + +**类型** +```typescript +type FindAndCountOptions = CommonFindOptions +``` + + +### `create()` + +创建关联对象 + +**签名** + +* `async create(options?: CreateOptions): Promise` + + + +### `update()` + +更新符合条件的关联对象 + +**签名** + +* `async update(options?: UpdateOptions): Promise` + + + +### `destroy()` + +删除符合条件的关联对象 + +**签名** + +* `async destroy(options?: TK | DestroyOptions): Promise` + + + +### `add()` + +添加对象关联关系 + +**签名** +* `async add(options: TargetKey | TargetKey[] | AssociatedOptions)` + +**类型** +```typescript +interface AssociatedOptions extends Transactionable { + tk?: TargetKey | TargetKey[]; +} +``` + +**详细信息** + +* `tk` - 关联对象的 targetKey 值,可以是单个值,也可以是数组。 + + +### `remove()` + +移除与给定对象之间的关联关系 + +**签名** +* `async remove(options: TargetKey | TargetKey[] | AssociatedOptions)` + +**详细信息** + +参数同 [`add()`](#add) 方法。 + +### `set()` + +设置当前关系的关联对象 + +**签名** + +* `async set(options: TargetKey | TargetKey[] | AssociatedOptions)` + +**详细信息** + +参数同 [`add()`](#add) 方法。 + + diff --git a/docs/tr-TR/api/database/relation-repository/has-one-repository.md b/docs/tr-TR/api/database/relation-repository/has-one-repository.md new file mode 100644 index 000000000..fd1037604 --- /dev/null +++ b/docs/tr-TR/api/database/relation-repository/has-one-repository.md @@ -0,0 +1,181 @@ +# HasOneRepository +`HasOneRepository` 为 `HasOne` 类型的关联 Repository。 + +```typescript +const User = db.collection({ + name: 'users', + fields: [ + { type: 'hasOne', name: 'profile' }, + { type: 'string', name: 'name' }, + ], +}); + +const Profile = db.collection({ + name: 'profiles', + fields: [{ type: 'string', name: 'avatar' }], +}); + +const user = await User.repository.create({ + values: { name: 'u1' }, +}); + +// 创建 HasOneRepository 实例 +const userProfileRepository = new HasOneRepository(User, 'profile', user.get('id')); + +``` + +## 类方法 + +### `find()` + +查找关联对象 + +**签名** + +* `async find(options?: SingleRelationFindOption): Promise | null>` + +**类型** + +```typescript +interface SingleRelationFindOption extends Transactionable { + fields?: Fields; + except?: Except; + appends?: Appends; + filter?: Filter; +} +``` + +**详细信息** + +查询参数与 [`Repository.find()`](../repository.md#find) 一致。 + +**示例** + +```typescript +const profile = await UserProfileRepository.find(); +// 关联对象不存在时,返回 null +``` + +### `create()` +创建关联对象 + +**签名** + +* `async create(options?: CreateOptions): Promise` + + + +**示例** + +```typescript +const profile = await UserProfileRepository.create({ + values: { avatar: 'avatar1' }, +}); + +console.log(profile.toJSON()); +/* +{ + id: 1, + avatar: 'avatar1', + userId: 1, + updatedAt: 2022-09-24T13:59:40.025Z, + createdAt: 2022-09-24T13:59:40.025Z +} +*/ + +``` + +### `update()` + +更新关联对象 + +**签名** + +* `async update(options: UpdateOptions): Promise` + + + + +**示例** + +```typescript +const profile = await UserProfileRepository.update({ + values: { avatar: 'avatar2' }, +}); + +profile.get('avatar'); // 'avatar2' +``` + +### `remove()` + +移除关联对象,仅解除关联关系,不删除关联对象 + +**签名** + +* `async remove(options?: Transactionable): Promise` + +**详细信息** + +* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 + +**示例** + +```typescript +await UserProfileRepository.remove(); +await UserProfileRepository.find() == null; // true + +await Profile.repository.count() === 1; // true +``` + +### `destroy()` + +删除关联对象 + +**签名** + +* `async destroy(options?: Transactionable): Promise` + + +**详细信息** + +* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 + +**示例** + +```typescript +await UserProfileRepository.destroy(); +await UserProfileRepository.find() == null; // true +await Profile.repository.count() === 0; // true +``` + +### `set()` + +设置关联对象 + +**签名** + +* `async set(options: TargetKey | SetOption): Promise` + +**类型** + +```typescript +interface SetOption extends Transactionable { + tk?: TargetKey; +} +```` +**详细信息** + +* tk: 设置关联对象的 targetKey +* transaction: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 + +**示例** + +```typescript +const newProfile = await Profile.repository.create({ + values: { avatar: 'avatar2' }, +}); + +await UserProfileRepository.set(newProfile.get('id')); + +(await UserProfileRepository.find()).get('id') === newProfile.get('id'); // true +``` diff --git a/docs/tr-TR/api/database/relation-repository/index.md b/docs/tr-TR/api/database/relation-repository/index.md new file mode 100644 index 000000000..27140968d --- /dev/null +++ b/docs/tr-TR/api/database/relation-repository/index.md @@ -0,0 +1,45 @@ +# RelationRepository + +`RelationRepository` 是关系类型的 `Repository` 对象,`RelationRepository` 可以实现在不加载关联的情况下对关联数据进行操作。基于 `RelationRepository`,每种关联都派生出对应的实现,分别为 + +* [`HasOneRepository`](#has-one-repository) +* `HasManyRepository` +* `BelongsToRepository` +* `BelongsToManyRepository` + + +## 构造函数 + +**签名** + +* `constructor(sourceCollection: Collection, association: string, sourceKeyValue: string | number)` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `sourceCollection` | `Collection` | - | 关联中的参照关系(referencing relation)对应的 Collection | +| `association` | `string` | - | 关联名称 | +| `sourceKeyValue` | `string \| number` | - | 参照关系中对应的 key 值 | + + +## 基类属性 + +### `db: Database` + +数据库对象 + +### `sourceCollection` +关联中的参照关系(referencing relation)对应的 Collection + +### `targetCollection` +关联中被参照关系(referenced relation)对应的 Collection + +### `association` +sequelize 中的与当前关联对应的 association 对象 + +### `associationField` +collection 中的与当前关联对应的字段 + +### `sourceKeyValue` +参照关系中对应的 key 值 diff --git a/docs/tr-TR/api/database/repository.md b/docs/tr-TR/api/database/repository.md new file mode 100644 index 000000000..f80fde92f --- /dev/null +++ b/docs/tr-TR/api/database/repository.md @@ -0,0 +1,352 @@ +# Repository + +数据表数据仓库管理类。大部分基于数据表的数据存取等操作均通过该类实现。 + +## 构造函数 + +通常不会直接由开发者调用,主要通过 `db.registerRepositories()` 注册类型以后,在 `db.colletion()` 的参数中指定对应已注册的仓库类型,并完成实例化。 + +**签名** + +* `constructor(collection: Collection)` + +**示例** + +```ts +import { Repository } from '@nocobase/database'; + +class MyRepository extends Repository { + async myQuery(sql) { + return this.database.sequelize.query(sql); + } +} + +db.registerRepositories({ + books: MyRepository +}); + +db.collection({ + name: 'books', + // here link to the registered repository + repository: 'books' +}); + +await db.sync(); + +const books = db.getRepository('books') as MyRepository; +await books.myQuery('SELECT * FROM books;'); +``` + +## 实例成员 + +### `database` + +上下文所在的数据库管理实例。 + +### `collection` + +对应的数据表管理实例。 + +### `model` + +对应的数据模型类。 + +## 实例方法 + + +### `find()` + +从数据库查询特定条件的数据集。相当于 Sequelize 中的 `Model.findAll()`。 + +**签名** + +* `async find(options?: FindOptions): Promise` + +**类型** +```typescript +type Filter = FilterWithOperator | FilterWithValue | FilterAnd | FilterOr; +type Appends = string[]; +type Except = string[]; +type Fields = string[]; +type Sort = string[] | string; + +interface SequelizeFindOptions { + limit?: number; + offset?: number; +} + +interface FilterByTk { + filterByTk?: TargetKey; +} + +interface CommonFindOptions extends Transactionable { + filter?: Filter; + fields?: Fields; + appends?: Appends; + except?: Except; + sort?: Sort; +} + +type FindOptions = SequelizeFindOptions & CommonFindOptions & FilterByTk; +``` + +**详细信息** + +#### `filter: Filter` +查询条件,用于过滤数据结果。传入的查询参数中,`key` 为查询的字段名,`value` 可传要查询的值, +也可配合使用操作符进行其他条件的数据筛选。 + +```typescript +// 查询 name 为 foo,并且 age 大于 18 的记录 +repository.find({ + filter: { + name: "foo", + age: { + $gt: 18, + }, + } +}) +``` +更多操作符请参考 [查询操作符](./operators.md)。 + +#### `filterByTk: TargetKey` +通过 `TargetKey` 查询数据,为 `filter` 参数的便捷方法。`TargetKey` 具体是哪一个字段, +可在 `Collection` 中进行[配置](./collection.md#filtertargetkey),默认为 `primaryKey`。 + +```typescript + +// 默认情况下,查找 id 为 1 的记录 +repository.find({ + filterByTk: 1, +}); + +``` + +#### `fields: string[]` +查询列,用户控制数据字段结果。传入此参数之后,只会返回指定的字段。 + +#### `except: string[]` +排除列,用于控制数据字段结果。传入此参数之后,传入的字段将不会输出。 + +#### `appends: string[]` +追加列,用于加载关联数据。传入此参数之后,指定的关联字段将一并输出。 + +#### `sort: string[] | string` +指定查询结果排序方式,传入参数为字段名称,默认按照升序 `asc` 排序,若需按降序 `desc` 排序, +可在字段名称前加上 `-` 符号,如:`['-id', 'name']`,表示按 `id desc, name asc` 排序。 + +#### `limit: number` +限制结果数量,同 `SQL` 中的 `limit` + +#### `offset: number` +查询偏移量,同 `SQL` 中的 `offset` + +**示例** + +```ts +const posts = db.getRepository('posts'); + +const results = await posts.find({ + filter: { + createdAt: { + $gt: '2022-01-01T00:00:00.000Z', + } + }, + fields: ['title'], + appends: ['user'], +}); +``` + +### `findOne()` + +从数据库查询特定条件的单条数据。相当于 Sequelize 中的 `Model.findOne()`。 + +**签名** + +* `async findOne(options?: FindOneOptions): Promise` + + + +**示例** + +```ts +const posts = db.getRepository('posts'); + +const result = await posts.findOne({ + filterByTk: 1, +}); +``` + +### `count()` + +从数据库查询特定条件的数据总数。相当于 Sequelize 中的 `Model.count()`。 + +**签名** + +* `count(options?: CountOptions): Promise` + +**类型** +```typescript +interface CountOptions extends Omit, Transactionable { + filter?: Filter; +} +``` + +**示例** + +```ts +const books = db.getRepository('books'); + +const count = await books.count({ + filter: { + title: '三字经' + } +}); +``` + + +### `findAndCount()` + +从数据库查询特定条件的数据集和结果数。相当于 Sequelize 中的 `Model.findAndCountAll()`。 + +**签名** + +* `async findAndCount(options?: FindAndCountOptions): Promise<[Model[], number]>` + +**类型** +```typescript +type FindAndCountOptions = Omit & CommonFindOptions; +``` + +**详细信息** + +查询参数与 `find()` 相同。返回值为一个数组,第一个元素为查询结果,第二个元素为结果总数。 + +### `create()` + +向数据表插入一条新创建的数据。相当于 Sequelize 中的 `Model.create()`。当要创建的数据对象携带关系字段的信息时,会一并创建或更新相应的关系数据记录。 + +**签名** + +* `async create(options: CreateOptions): Promise` + + + +**示例** + +```ts +const posts = db.getRepository('posts'); + +const result = await posts.create({ + values: { + title: 'NocoBase 1.0 发布日志', + tags: [ + // 有关系表主键值时为更新该条数据 + { id: 1 }, + // 没有主键值时为创建新数据 + { name: 'NocoBase' }, + ] + }, +}); +``` + +### `createMany()` + +向数据表插入多条新创建的数据。相当于多次调用 `create()` 方法。 + +**签名** + +* `createMany(options: CreateManyOptions): Promise` + +**类型** +```typescript +interface CreateManyOptions extends BulkCreateOptions { + records: Values[]; +} +``` + +**详细信息** + +* `records`:要创建的记录的数据对象数组。 +* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 + +**示例** + +```ts +const posts = db.getRepository('posts'); + +const results = await posts.createMany({ + records: [ + { + title: 'NocoBase 1.0 发布日志', + tags: [ + // 有关系表主键值时为更新该条数据 + { id: 1 }, + // 没有主键值时为创建新数据 + { name: 'NocoBase' }, + ] + }, + { + title: 'NocoBase 1.1 发布日志', + tags: [ + { id: 1 } + ] + }, + ], +}); +``` + +### `update()` + +更新数据表中的数据。相当于 Sequelize 中的 `Model.update()`。当要更新的数据对象携带关系字段的信息时,会一并创建或更新相应的关系数据记录。 + +**签名** + +* `async update(options: UpdateOptions): Promise` + + + +**示例** + +```ts +const posts = db.getRepository('posts'); + +const result = await posts.update({ + filterByTk: 1, + values: { + title: 'NocoBase 1.0 发布日志', + tags: [ + // 有关系表主键值时为更新该条数据 + { id: 1 }, + // 没有主键值时为创建新数据 + { name: 'NocoBase' }, + ] + }, +}); +``` + +### `destory()` + +删除数据表中的数据。相当于 Sequelize 中的 `Model.destroy()`。 + +**签名** + +* `async destory(options?: TargetKey | TargetKey[] | DestoryOptions): Promise` + +**类型** + +```typescript +interface DestroyOptions extends SequelizeDestroyOptions { + filter?: Filter; + filterByTk?: TargetKey | TargetKey[]; + truncate?: boolean; + context?: any; +} +``` + +**详细信息** + +* `filter`:指定要删除的记录的过滤条件。Filter 详细用法可参考 [`find()`](#find) 方法。 +* `filterByTk`:按 TargetKey 指定要删除的记录的过滤条件。 +* `truncate`: 是否清空表数据,在没有传入 `filter` 或 `filterByTk` 参数时有效。 +* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 diff --git a/docs/tr-TR/api/database/shared.md b/docs/tr-TR/api/database/shared.md new file mode 100644 index 000000000..fd2b409fb --- /dev/null +++ b/docs/tr-TR/api/database/shared.md @@ -0,0 +1,8 @@ +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options.values` | `M` | `{}` | 插入的数据对象 | +| `options.whitelist?` | `string[]` | - | `values` 字段的白名单,只有名单内的字段会被存储 | +| `options.blacklist?` | `string[]` | - | `values` 字段的黑名单,名单内的字段不会被存储 | +| `options.transaction?` | `Transaction` | - | 事务 | diff --git a/docs/tr-TR/api/database/shared/create-options.md b/docs/tr-TR/api/database/shared/create-options.md new file mode 100644 index 000000000..51b245a45 --- /dev/null +++ b/docs/tr-TR/api/database/shared/create-options.md @@ -0,0 +1,21 @@ +**类型** +```typescript +type WhiteList = string[]; +type BlackList = string[]; +type AssociationKeysToBeUpdate = string[]; + +interface CreateOptions extends SequelizeCreateOptions { + values?: Values; + whitelist?: WhiteList; + blacklist?: BlackList; + updateAssociationValues?: AssociationKeysToBeUpdate; + context?: any; +} +``` + +**详细信息** + +* `values`:要创建的记录的数据对象。 +* `whitelist`:指定要创建的记录的数据对象中,哪些字段**可以被写入**。若不传入此参数,则默认允许所有字段写入。 +* `blacklist`:指定要创建的记录的数据对象中,哪些字段**不允许被写入**。若不传入此参数,则默认允许所有字段写入。 +* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 diff --git a/docs/tr-TR/api/database/shared/destroy-options.md b/docs/tr-TR/api/database/shared/destroy-options.md new file mode 100644 index 000000000..aaf8e63b9 --- /dev/null +++ b/docs/tr-TR/api/database/shared/destroy-options.md @@ -0,0 +1,17 @@ +**类型** + +```typescript +interface DestroyOptions extends SequelizeDestroyOptions { + filter?: Filter; + filterByTk?: TargetKey | TargetKey[]; + truncate?: boolean; + context?: any; +} +``` + +**详细信息** + +* `filter`:指定要删除的记录的过滤条件。Filter 详细用法可参考 [`find()`](#find) 方法。 +* `filterByTk`:按 TargetKey 指定要删除的记录的过滤条件。 +* `truncate`: 是否清空表数据,在没有传入 `filter` 或 `filterByTk` 参数时有效。 +* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 diff --git a/docs/tr-TR/api/database/shared/find-one.md b/docs/tr-TR/api/database/shared/find-one.md new file mode 100644 index 000000000..47e5f2192 --- /dev/null +++ b/docs/tr-TR/api/database/shared/find-one.md @@ -0,0 +1,8 @@ +**类型** +```typescript +type FindOneOptions = Omit; +``` + +**参数** + +大部分参数与 `find()` 相同,不同之处在于 `findOne()` 只返回单条数据,所以不需要 `limit` 参数,且查询时始终为 `1`。 diff --git a/docs/tr-TR/api/database/shared/find-options.md b/docs/tr-TR/api/database/shared/find-options.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/tr-TR/api/database/shared/transaction.md b/docs/tr-TR/api/database/shared/transaction.md new file mode 100644 index 000000000..27068fcf9 --- /dev/null +++ b/docs/tr-TR/api/database/shared/transaction.md @@ -0,0 +1 @@ +* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 diff --git a/docs/tr-TR/api/database/shared/update-options.md b/docs/tr-TR/api/database/shared/update-options.md new file mode 100644 index 000000000..ea03c3ea7 --- /dev/null +++ b/docs/tr-TR/api/database/shared/update-options.md @@ -0,0 +1,24 @@ +**类型** +```typescript +interface UpdateOptions extends Omit { + values: Values; + filter?: Filter; + filterByTk?: TargetKey; + whitelist?: WhiteList; + blacklist?: BlackList; + updateAssociationValues?: AssociationKeysToBeUpdate; + context?: any; +} +``` + +**详细信息** + + +* `values`:要更新的记录的数据对象。 +* `filter`:指定要更新的记录的过滤条件, Filter 详细用法可参考 [`find()`](#find) 方法。 +* `filterByTk`:按 TargetKey 指定要更新的记录的过滤条件。 +* `whitelist`: `values` 字段的白名单,只有名单内的字段会被写入。 +* `blacklist`: `values` 字段的黑名单,名单内的字段不会被写入。 +* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。 + +`filterByTk` 与 `filter` 至少要传其一。 diff --git a/docs/tr-TR/api/env.md b/docs/tr-TR/api/env.md new file mode 100644 index 000000000..e3140bfff --- /dev/null +++ b/docs/tr-TR/api/env.md @@ -0,0 +1,225 @@ +# 环境变量 + +## 全局环境变量 + +保存在 `.env` 文件里 + +### APP_ENV + +应用环境,默认值 `development`,可选项包括: + +- `production` 生产环境 +- `development` 开发环境 + +```bash +APP_ENV=production +``` + +### APP_HOST + +应用主机,默认值 `0.0.0.0` + +```bash +APP_HOST=192.168.3.154 +``` + +### APP_PORT + +应用端口,默认值 `13000` + +```bash +APP_PORT=13000 +``` + +### APP_KEY + +秘钥,用于 jwt 等场景 + +```bash +APP_KEY=app-key-test +``` + +### API_BASE_PATH + +NocoBase API 地址前缀,默认值 `/api/` + +```bash +API_BASE_PATH=/api/ +``` + +### PLUGIN_PACKAGE_PREFIX + +插件包前缀,默认值 `@nocobase/plugin-,@nocobase/preset-` + +例如,有一名为 `my-nocobase-app` 的项目,新增了 `hello` 插件,包名为 `@my-nocobase-app/plugin-hello`。 + +PLUGIN_PACKAGE_PREFIX 配置如下: + +```bash +PLUGIN_PACKAGE_PREFIX=@nocobase/plugin-,@nocobase/preset-,@my-nocobase-app/plugin- +``` + +插件名和包名的对应关系为: + +- `users` 插件包名为 `@nocobase/plugin-users` +- `nocobase` 插件包名为 `@nocobase/preset-nocobase` +- `hello` 插件包名为 `@my-nocobase-app/plugin-hello` + +### DB_DIALECT + +数据库类型,默认值 `sqlite`,可选项包括: + +- `sqlite` +- `mysql` +- `postgres` + +```bash +DB_DIALECT=mysql +``` + +### DB_STORAGE + +数据库文件路径(使用 SQLite 数据库时配置) + +```bash +# 相对路径 +DB_HOST=storage/db/nocobase.db +# 绝对路径 +DB_HOST=/your/path/nocobase.db +``` + +### DB_HOST + +数据库主机(使用 mysql 或 postgres 数据库时需要配置) + +默认值 `localhost` + +```bash +DB_HOST=localhost +``` + +### DB_PORT + +数据库端口(使用 mysql 或 postgres 数据库时需要配置) + +- MySQL 默认端口 3306 +- PostgreSQL 默认端口 5432 + +```bash +DB_PORT=3306 +``` + +### DB_DATABASE + +数据库名(使用 mysql 或 postgres 数据库时需要配置) + +```bash +DB_DATABASE=nocobase +``` + +### DB_USER + +数据库用户(使用 mysql 或 postgres 数据库时需要配置) + +```bash +DB_USER=nocobase +``` + +### DB_PASSWORD + +数据库密码(使用 mysql 或 postgres 数据库时需要配置) + +```bash +DB_PASSWORD=nocobase +``` + +### DB_TABLE_PREFIX + +数据表前缀 + +```bash +DB_TABLE_PREFIX=nocobase_ +``` + +### DB_LOGGING + +数据库日志开关,默认值 `off`,可选项包括: + +- `on` 打开 +- `off` 关闭 + +```bash +DB_LOGGING=on +``` + +## 临时环境变量 + +安装 NocoBase 时,可以通过设置临时的环境变量来辅助安装,如: + +```bash +yarn cross-env \ + INIT_APP_LANG=zh-CN \ + INIT_ROOT_EMAIL=demo@nocobase.com \ + INIT_ROOT_PASSWORD=admin123 \ + INIT_ROOT_NICKNAME="Super Admin" \ + nocobase install + +# 等同于 +yarn nocobase install \ + --lang=zh-CN \ + --root-email=demo@nocobase.com \ + --root-password=admin123 \ + --root-nickname="Super Admin" + +# 等同于 +yarn nocobase install -l zh-CN -e demo@nocobase.com -p admin123 -n "Super Admin" +``` + +### INIT_APP_LANG + +安装时的语言,默认值 `en-US`,可选项包括: + +- `en-US` +- `zh-CN` + +```bash +yarn cross-env \ + INIT_APP_LANG=zh-CN \ + nocobase install +``` + +### INIT_ROOT_EMAIL + +Root 用户邮箱 + +```bash +yarn cross-env \ + INIT_APP_LANG=zh-CN \ + INIT_ROOT_EMAIL=demo@nocobase.com \ + nocobase install +``` + +### INIT_ROOT_PASSWORD + +Root 用户密码 + +```bash +yarn cross-env \ + INIT_APP_LANG=zh-CN \ + INIT_ROOT_EMAIL=demo@nocobase.com \ + INIT_ROOT_PASSWORD=admin123 \ + nocobase install +``` + +### INIT_ROOT_NICKNAME + +Root 用户昵称 + +```bash +yarn cross-env \ + INIT_APP_LANG=zh-CN \ + INIT_ROOT_EMAIL=demo@nocobase.com \ + INIT_ROOT_PASSWORD=admin123 \ + INIT_ROOT_NICKNAME="Super Admin" \ + nocobase install +``` diff --git a/docs/tr-TR/api/http/action-api.md b/docs/tr-TR/api/http/action-api.md new file mode 100644 index 000000000..1445d05e5 --- /dev/null +++ b/docs/tr-TR/api/http/action-api.md @@ -0,0 +1,142 @@ +# Action API + +## Common + +--- + +Collection and Association resources are common. + +### `create` + +```bash +POST /api/users:create?whitelist=a,b&blacklist=c,d + +{} # Request Body +``` + +- Parameters + - whitelist White list + - blacklist Black list +- Request body: JSON data to be inserted +- Response body data: Created data JSON + +#### Add a User + +```bash +POST /api/users:create + +Request Body +{ + "email": "demo@nocobase.com", + "name": "Admin" +} + +Response 200 (application/json) +{ + "data": {}, +} +``` + +#### Add a user's article + +```bash +POST /api/users/1/posts:create + +Request Body +{ + "title": "My first post" +} + +Response 200 (application/json) +{ + "data": {}, +} +``` + +#### Association in Request Body + +```bash +POST /api/posts:create + +Request Body +{ + "title": "My first post", + "user": 1 +} + +Response 200 (application/json) +{ + "data": { + "id": 1, + "title": "My first post", + "userId": 1, + "user": { + "id": 1 + } + } +} +``` + +### `update` + +```bash +POST /api/users:create?filterByTk=1&whitelist=a,b&blacklist=c,d + +{} # Request Body +``` + +- Parameters + - whitelist White list + - blacklist Black list + - filterByTk Filter by tk field, by default tk is the primary key of the data table + - filter Filter,support json string +- Request body: JSON data to be updated + +#### Association in Request Body + +```bash +POST /api/posts:update/1 + +Request Body +{ + "title": "My first post 2", + "user": 2 +} + +Response 200 (application/json) +{ + "data": [ + { + "id": 1, + "title": "My first post 2", + "userId": 2, + "user": { + "id": 2 + } + } + ] +} +``` + +### `list` + +### `get` + +### `destroy` + +### `move` + +## Association + +--- + +### `add` + +### `set` + +### `remove` + +### `toggle` + + + diff --git a/docs/tr-TR/api/http/filter-operators.md b/docs/tr-TR/api/http/filter-operators.md new file mode 100644 index 000000000..a31112c5c --- /dev/null +++ b/docs/tr-TR/api/http/filter-operators.md @@ -0,0 +1,59 @@ +# Filter operators + +## Common + +- $eq +- $ne +- $gte +- $gt +- $lte +- $lt +- $not +- $is +- $in +- $notIn +- $like +- $notLike +- $iLike +- $notILike +- $and +- $or +- $empty +- $notEmpty + +## array + +- $match +- $notMatch +- $anyOf +- $noneOf +- $arrayEmpty +- $arrayNotEmpty + +## association + +- $exists +- $notExists + +## boolean + +- $isTruthy +- $isFalsy + +## date + +- $dateOn +- $dateNotOn +- $dateBefore +- $dateNotBefore +- $dateAfter +- $dateNotAfter + +## string + +- $includes +- $notIncludes +- $startsWith +- $notStartsWith +- $endWith +- $notEndWith diff --git a/docs/tr-TR/api/http/index.md b/docs/tr-TR/api/http/index.md new file mode 100644 index 000000000..8d9323d65 --- /dev/null +++ b/docs/tr-TR/api/http/index.md @@ -0,0 +1,301 @@ +# Overview + +NocoBase HTTP API is designed based on Resource & Action, it is a superset of REST API. The operation is not limited to add, delete, change, and check, Resource Action can be extended arbitrarily in NocoBase. + +## Resource + +Resource has two expressions in NocoBase. + +- `` +- `.` + + + +- collection is the set of all abstract data +- association is the association data for the collection +- resource includes both collection and collection.association + + + +### Example + +- `posts` Post +- `posts.user` Post user +- `posts.tags` Post tags + +## Action + +Representing resource operations as `:` + +- `:` +- `.:` + +Built-in global operations for collection or association + +- `create` +- `get` +- `list` +- `update` +- `destroy` +- `move` + +Built-in association operation for association only + +- `set` +- `add` +- `remove` +- `toggle` + +### Example + +- `posts:create` Create posts +- `posts.user:get` View posts user +- `posts.tags:add` Attach post tags (associate existing tags with post) + +## Request URL + +```bash + /api/: + /api/:/ + /api///: + /api///:/ +``` + +### Example + +posts resource + +```bash +POST /api/posts:create +GET /api/posts:list +GET /api/posts:get/1 +POST /api/posts:update/1 +POST /api/posts:destroy/1 +``` + +posts.comments resource + +```bash +POST /api/posts/1/comments:create +GET /api/posts/1/comments:list +GET /api/posts/1/comments:get/1 +POST /api/posts/1/comments:update/1 +POST /api/posts/1/comments:destroy/1 +``` + +posts.tags resource + +```bash +POST /api/posts/1/tags:create +GET /api/posts/1/tags:get +GET /api/posts/1/tags:list +POST /api/posts/1/tags:update +POST /api/posts/1/tags:destroy +POST /api/posts/1/tags:add +GET /api/posts/1/tags:remove +``` + +## Resource location + +- collection resource, locates the data to be processed by `collectionIndex`, `collectionIndex` must be unique +- association resource, locates the data to be processed by `collectionIndex` and `associationIndex` jointly, `associationIndex` may not be unique, but `collectionIndex` and `associationIndex`'s association indexes must be unique + +When viewing association resource details, the requested URL needs to provide both `` and ``, `` is not redundant because `` may not be unique. + +For example, `tables.fields` indicates the fields of a data table + +```bash +GET /api/tables/table1/fields/title +GET /api/tables/table2/fields/title +``` + +Both table1 and table2 have a title field. The title is unique in table1, but other tables may also have a title field + +## Request parameters + +Request parameters can be placed in the request's headers, parameters (query string), and body (GET requests do not have a body). + +A few special request parameters + +- `filter` Data filtering, used in query-related operations. +- `filterByTk` filter by tk field, used in operations that specify details of the data. +- `sort` Sorting, used in query-related operations. +- `fields` which data to output, for use in query-related operations. +- `appends` additional relationship fields for use in query-related operations. +- `except` which fields to exclude (no output), used in query-related operations. +- `whitelist` fields whitelist, used in data creation and update related operations. +- `blacklist` fields blacklist, used in data creation and update related operations. + +### filter + +Data filter + +```bash +# simple +GET /api/posts?filter[status]=publish +# Recommend using the json string format, which requires encodeURIComponent encoding +GET /api/posts?filter={"status":"published"} + +# filter operators +GET /api/posts?filter[status.$eq]=publish +GET /api/posts?filter={"status.$eq":"published"} + +# $and +GET /api/posts?filter={"$and": [{"status.$eq":"published"}, {"title.$includes":"a"}]} +# $or +GET /api/posts?filter={"$or": [{"status.$eq":"pending"}, {"status.$eq":"draft"}]} + +# association field +GET /api/posts?filter[user.email.$includes]=gmail +GET /api/posts?filter={"user.email.$includes":"gmail"} +``` + +[Click here for more information about filter operators](http-api/filter-operators) + +### filterByTk + +Filter by tk field. By default + +- collection resource, tk is the primary key of the data table. +- association resource, tk is the targetKey field of the association. + +```bash +GET /api/posts:get?filterByTk=1&fields=name,title&appends=tags +``` + +### sort + +Sorting. When sorting in descending order, the fields are preceded by the minus sign `-`. + +```bash +# createAt field in ascending order +GET /api/posts:get?sort=createdAt +# createAt field descending +GET /api/posts:get?sort=-createdAt +# Multiple fields sorted jointly, createAt field descending, title A-Z ascending +GET /api/posts:get?sort=-createdAt,title +``` + +### fields + +Which fields to output + +```bash +GET /api/posts:list?fields=name,title + +Response 200 (application/json) +{ + "data": [ + { + "name": "", + "title": "" + } + ], + "meta": {} +} +``` + +### appends + +Appends a relationship field + +### except + +Which fields to exclude (not output) for use in query-related operations. + +### whitelist + +Whitelist + +```bash +POST /api/posts:create?whitelist=title + +{ + "title": "My first post", + "date": "2022-05-19" # The date field will be filtered out and will not be written to the database +} +``` + +### blacklist + +Blacklist + +```bash +POST /api/posts:create?blacklist=date + +{ + "title": "My first post", + "date": "2022-05-19" # The date field will be filtered out and will not be written to the database +} +``` + +## Request Response + +Format of the response + +```ts +type ResponseResult = { + data?: any; // Master data + meta?: any; // Additional Data + errors?: ResponseError[]; // Errors +}; + +type ResponseError = { + code?: string; + message: string; +}; +``` + +### Example + +View list + +```bash +GET /api/posts:list + +Response 200 (application/json) + +{ + data: [ + { + id: 1 + } + ], + meta: { + count: 1 + page: 1, + pageSize: 1, + totalPage: 1 + }, +} +``` + +View details + +```bash +GET /api/posts:get/1 + +Response 200 (application/json) + +{ + data: { + id: 1 + } +} +``` + +Error + +```bash +POST /api/posts:create + +Response 400 (application/json) + +{ + errors: [ + { + message: 'name must be required', + }, + ], +} +``` diff --git a/docs/tr-TR/api/http/javascript-sdk.md b/docs/tr-TR/api/http/javascript-sdk.md new file mode 100644 index 000000000..329441c51 --- /dev/null +++ b/docs/tr-TR/api/http/javascript-sdk.md @@ -0,0 +1,281 @@ +# JavaScript SDK + +## APIClient + +```ts +class APIClient { + // axios instance + axios: AxiosInstance; + // constructors + constructor(instance?: AxiosInstance | AxiosRequestConfig); + // Client-side requests, support for AxiosRequestConfig and ResourceActionOptions + request, D = any>(config: AxiosRequestConfig | ResourceActionOptions): Promise; + // Get Resources + resource(name: string, of?: any): R; +} +``` + +Initialize instance + +```ts +import axios from 'axios'; +import { APIClient } from '@nocobase/sdk'; + +// Provide AxiosRequestConfig configuration parameters +const api = new APIClient({ + baseURL: 'https://localhost:8000/api', +}); + +// Provide AxiosInstance +const instance = axios.create({ + baseURL: 'https://localhost:8000/api', +}); +const api = new APIClient(instance); +``` + +## Mock + +```ts +import { APIClient } from '@nocobase/sdk'; +import MockAdapter from 'axios-mock-adapter'; + +const api = new APIClient({ + baseURL: 'https://localhost:8000/api', +}); + +const mock = new MockAdapter(api.axios); + +mock.onGet('users:get').reply(200, { + data: { id: 1, name: 'John Smith' }, +}); + +await api.request({ url: 'users:get' }); +``` + +## Storage + +APIClient uses localStorage by default, you can also custom storage. + +```ts +import { Storage } from '@nocobase/sdk'; + +class MemoryStorage extends Storage { + items = new Map(); + + clear() { + this.items.clear(); + } + + getItem(key: string) { + return this.items.get(key); + } + + setItem(key: string, value: string) { + return this.items.set(key, value); + } + + removeItem(key: string) { + return this.items.delete(key); + } +} + +const api = new APIClient({ + baseURL: 'https://localhost:8000/api', + storageClass: CustomStorage, +}); +``` + +## Auth + +```ts +// sign in and remember the current token +api.auth.signIn({ email, password }); +// sign out and delete the token +api.auth.signOut(); +// set the token +api.auth.setToken('123'); +// set the role (multiple roles) +api.auth.setRole('admin'); +// set the locale (multiple languages) +api.auth.setLocale('zh-CN'); +``` + +Custom Auth + +```ts +import { Auth } from '@nocobase/sdk'; + +class CustomAuth extends Auth { + +} + +const api = new APIClient({ + baseURL: 'https://localhost:8000/api', + authClass: CustomAuth, +}); +``` + +## Request + +```ts +// url +await api.request({ + url: 'users:list', + // request params + params: { + filter: { + 'email.$includes': 'noco', + }, + }, + // request body + data, +}); + +// resource & action +await api.request({ + resource: 'users', + action: 'list', + // action params + params: { + filter: { + 'email.$includes': 'noco', + }, + page: 1, + }, +}); +``` + +## Resource action + +```ts +await api.resource('collection')[action](); +await api.resource('collection.association', collectionId)[action](); +``` + +## Action API + +```ts +await api.resource('collection').create(); +await api.resource('collection').get(); +await api.resource('collection').list(); +await api.resource('collection').update(); +await api.resource('collection').destroy(); +await api.resource('collection.association', collectionId).create(); +await api.resource('collection.association', collectionId).get(); +await api.resource('collection.association', collectionId).list(); +await api.resource('collection.association', collectionId).update(); +await api.resource('collection.association', collectionId).destroy(); +``` + +### `get` + +```ts +interface Resource { + get: (options?: GetActionOptions) => Promise; +} + +interface GetActionOptions { + filter?: any; + filterByTk?: any; + fields?: string || string[]; + appends?: string || string[]; + expect?: string || string[]; + sort?: string[]; +} +``` + +### `list` + +```ts +interface Resource { + list: (options?: ListActionOptions) => Promise; +} + +interface ListActionOptions { + filter?: any; + filterByTk?: any; + fields?: string || string[]; + appends?: string || string[]; + expect?: string || string[]; + sort?: string[]; + page?: number; + pageSize?: number; + paginate?: boolean; +} +``` + +### `create` + +```ts +interface Resource { + create: (options?: CreateActionOptions) => Promise; +} + +interface CreateActionOptions { + whitelist?: string[]; + blacklist?: string[]; + values?: {[key: sting]: any}; +} +``` + +### `update` + +```ts +interface Resource { + update: (options?: UpdateActionOptions) => Promise; +} + +interface UpdateActionOptions { + filter?: any; + filterByTk?: any; + whitelist?: string[]; + blacklist?: string[]; + values?: {[key: sting]: any}; +} +``` + +### `destroy` + +```ts +interface Resource { + destroy: (options?: DestroyActionOptions) => Promise; +} + +interface DestroyActionOptions { + filter?: any; + filterByTk?: any; +} +``` + +### `move` + +```ts +interface Resource { + move: (options?: MoveActionOptions) => Promise; +} + +interface MoveActionOptions { + sourceId: any; + targetId?: any; + /** @default 'sort' */ + sortField?: any; + targetScope?: {[key: string]: any}; + sticky?: boolean; + method?: 'insertAfter' | 'prepend'; +} +``` + +### `` + +```ts +interface AttachmentResource { + +} + +interface UploadActionOptions { + +} + +api.resource('attachments').upload(); +api.resource('attachments').upload(); +``` diff --git a/docs/tr-TR/api/http/rest-api.md b/docs/tr-TR/api/http/rest-api.md new file mode 100644 index 000000000..ae3d9b81a --- /dev/null +++ b/docs/tr-TR/api/http/rest-api.md @@ -0,0 +1,184 @@ +# REST API + +NocoBase's HTTP API is a superset of the REST API, and the standard CRUD API also supports the RESTful style. + +## Collection resources + +--- + +### Create collection + +HTTP API + +```bash +POST /api/:create + +{} # JSON body +``` + +REST API + +```bash +POST /api/ + +{} # JSON body +``` + +### List collection + +HTTP API + +```bash +GET /api/:list +``` + +REST API + +```bash +GET /api/ +``` + +### View collection details + +HTTP API + +```bash +GET /api/:get?filterByTk= +GET /api/:get/ +``` + +REST API + +```bash +GET /api// +``` + +### Update collection + +HTTP API + +```bash +POST /api/:update?filterByTk= + +{} # JSON body + +# Or +POST /api/:update/ + +{} # JSON body +``` + +REST API + +```bash +PUT /api// + +{} # JSON body +``` + +### Delete collection + +HTTP API + +```bash +POST /api/:destroy?filterByTk= +# Or +POST /api/:destroy/ +``` + +REST API + +```bash +DELETE /api// +``` + +## Association resources + +--- + +### Create Association + +HTTP API + +```bash +POST /api///:create + +{} # JSON body +``` + +REST API + +```bash +POST /api/// + +{} # JSON body +``` + +### List Association + +HTTP API + +```bash +GET /api///:list +``` + +REST API + +```bash +GET /api/// +``` + +### View Association details + +HTTP API + +```bash +GET /api///:get?filterByTk= +# Or +GET /api///:get/ +``` + +REST API + +```bash +GET /api///:get/ +``` + +### Update Association + +HTTP API + +```bash +POST /api///:update?filterByTk= + +{} # JSON body + +# Or +POST /api///:update/ + +{} # JSON body +``` + +REST API + +```bash +PUT /api///:update/ + +{} # JSON +``` + +### Delete Association + +HTTP API + +```bash +POST /api///:destroy?filterByTk= +# Or +POST /api///:destroy/ +``` + +REST API + +```bash +DELETE /api//// +``` diff --git a/docs/tr-TR/api/index.md b/docs/tr-TR/api/index.md new file mode 100644 index 000000000..cb8199c15 --- /dev/null +++ b/docs/tr-TR/api/index.md @@ -0,0 +1,12 @@ +# 概览 + +| 模块 | 包名 | 描述 | +| --------------------------------- | --------------------- | ------------------- | +| [Server](/api/server) | `@nocobase/server` | 服务端应用 | +| [Database](/api/database) | `@nocobase/database` | 数据库访问层 | +| [Resourcer](/api/resourcer) | `@nocobase/resourcer` | 资源与路由映射 | +| [ACL](/api/acl) | `@nocobase/acl` | 访问控制表 | +| [Client](/api/client/application) | `@nocobase/client` | 客户端应用 | +| [CLI](/api/cli) | `@nocobase/cli` | NocoBase 命令行工具 | +| [SDK](/api/sdk) | `@nocobase/sdk` | NocoBase SDK | +| [Actions](/api/actions) | `@nocobase/actions` | 内置常用资源操作 | diff --git a/docs/tr-TR/api/resourcer/action.md b/docs/tr-TR/api/resourcer/action.md new file mode 100644 index 000000000..9b5a0968a --- /dev/null +++ b/docs/tr-TR/api/resourcer/action.md @@ -0,0 +1,148 @@ +# Action + +Action 是对资源的操作过程的描述,通常包含数据库处理等,类似其他框架中的 service 层,最简化的实现可以是一个 Koa 的中间件函数。在资源管理器里,针对特定资源定义的普通操作函数会被包装成 Action 类型的实例,当请求匹配对应资源的操作时,执行对应的操作过程。 + +## 构造函数 + +通常不需要直接实例化 Action,而是由资源管理器自动调用 `Action` 的静态方法 `toInstanceMap()` 进行实例化。 + +### `constructor(options: ActionOptions)` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `handler` | `Function` | - | 操作函数 | +| `middlewares?` | `Middleware \| Middleware[]` | - | 针对操作的中间件 | +| `values?` | `Object` | - | 默认的操作数据 | +| `fields?` | `string[]` | - | 默认针对的字段组 | +| `appends?` | `string[]` | - | 默认附加的关联字段组 | +| `except?` | `string[]` | - | 默认排除的字段组 | +| `filter` | `FilterOptions` | - | 默认的过滤参数 | +| `sort` | `string[]` | - | 默认的排序参数 | +| `page` | `number` | - | 默认的页码 | +| `pageSize` | `number` | - | 默认的每页数量 | +| `maxPageSize` | `number` | - | 默认最大每页数量 | + +## 实例成员 + +### `actionName` + +被实例化后对应的操作名称。在实例化时从请求中解析获取。 + +### `resourceName` + +被实例化后对应的资源名称。在实例化时从请求中解析获取。 + +### `resourceOf` + +被实例化后对应的关系资源的主键值。在实例化时从请求中解析获取。 + +### `readonly middlewares` + +针对操作的中间件列表。 + +### `params` + +操作参数。包含对应操作的所有相关参数,实例化时根据定义的 action 参数初始化,之后请求中解析前端传入的参数并根据对应参数的合并策略合并。如果有其他中间件的处理,也会有类似的合并过程。直到 handler 处理时,访问 params 得到的是经过多次合并的最终参数。 + +参数的合并过程提供了针对操作处理的可扩展性,可以通过自定义中间件的方式按业务需求进行参数的前置解析和处理,例如表单提交的参数验证就可以在此环节实现。 + +预设的参数可以参考 [/api/actions] 中不同操作的参数。 + +参数中还包含请求资源路由的描述部分,具体如下: + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `resourceName` | `string` | - | 资源名称 | +| `resourceIndex` | `string \| number` | - | 资源的主键值 | +| `associatedName` | `string` | - | 所属关系资源的名称 | +| `associatedIndex` | `string \| number` | - | 所属关系资源的主键值 | +| `associated` | `Object` | - | 所属关系资源的实例 | +| `actionName` | `string` | - | 操作名称 | + +**示例** + +```ts +app.resourcer.define('books', { + actions: { + publish(ctx, next) { + ctx.body = ctx.action.params.values; + // { + // id: 1234567890 + // publishedAt: '2019-01-01', + // } + } + }, + middlewares: [ + async (ctx, next) => { + ctx.action.mergeParams({ + values: { + id: Math.random().toString(36).substr(2, 10), + publishedAt: new Date(), + } + }); + await next(); + } + ] +}); +``` + +## 实例方法 + +### `mergeParams()` + +将额外的参数合并至当前参数集,且可以根据不同的策略进行合并。 + +**签名** + +* `mergeParams(params: ActionParams, strategies: MergeStrategies = {})` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `params` | `ActionParams` | - | 额外的参数集 | +| `strategies` | `MergeStrategies` | - | 针对每个参数的合并策略 | + +内置操作的默认合并策略如下表: + +| 参数名 | 类型 | 默认值 | 合并策略 | 描述 | +| --- | --- | --- | --- | --- | +| `filterByTk` | `number \| string` | - | SQL `and` | 查询主键值 | +| `filter` | `FilterOptions` | - | SQL `and` | 查询过滤参数 | +| `fields` | `string[]` | - | 取并集 | 字段组 | +| `appends` | `string[]` | `[]` | 取并集 | 附加的关联字段组 | +| `except` | `string[]` | `[]` | 取并集 | 排除的字段组 | +| `whitelist` | `string[]` | `[]` | 取交集 | 可处理字段的白名单 | +| `blacklist` | `string[]` | `[]` | 取并集 | 可处理字段的黑名单 | +| `sort` | `string[]` | - | SQL `order by` | 查询排序参数 | +| `page` | `number` | - | 覆盖 | 页码 | +| `pageSize` | `number` | - | 覆盖 | 每页数量 | +| `values` | `Object` | - | 深度合并 | 操作提交的数据 | + +**示例** + +```ts +ctx.action.mergeParams({ + filter: { + name: 'foo', + }, + fields: ['id', 'name'], + except: ['name'], + sort: ['id'], + page: 1, + pageSize: 10, + values: { + name: 'foo', + }, +}, { + filter: 'and', + fields: 'union', + except: 'union', + sort: 'overwrite', + page: 'overwrite', + pageSize: 'overwrite', + values: 'deepMerge', +}); +``` diff --git a/docs/tr-TR/api/resourcer/index.md b/docs/tr-TR/api/resourcer/index.md new file mode 100644 index 000000000..d1eb0c49b --- /dev/null +++ b/docs/tr-TR/api/resourcer/index.md @@ -0,0 +1,252 @@ +# Resourcer + +Resourcer 主要用于管理 API 资源与路由,也是 NocoBase 的内置模块,app 默认会自动创建一个 Resourcer 实例,大部分情况你可以通过 `app.resourcer` 访问。 + +资源路由管理器主要通过 [资源](/api/server/resourcer/resource) + [操作](/api/server/resourcer/action) 的概念定义服务端 API 接口,与 RESTful 的概念相似。大部分资源通过映射数据库表生成,包含常规的 CRUD 操作,以覆盖常见场景。但如果有额外需求,也可以在此基础上扩展更多的资源类型和操作类型。 + +## 包结构 + +可通过以下方式引入相关实体: + +```ts +import Resourcer, { + Resource, + Action, + Middleware, + branch +} from '@nocobase/resourcer'; +``` + +## 构造函数 + +用于创建 Resourcer 管理器实例。由于 app 默认创建一个内置实例,所以通常不会直接使用构造函数。 + +**签名** + +* `constructor(options: ResourcerOptions)` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `prefix` | `string` | - | 路由路径前缀 | +| `accessors` | `Object` | _以下成员值_ | 默认操作方法名称标识 | +| `accessors.list` | `string` | `'list'` | 列举操作方法名称标识 | +| `accessors.get` | `string` | `'get'` | 获取操作方法名称标识 | +| `accessors.create` | `string` | `'create'` | 创建操作方法名称标识 | +| `accessors.update` | `string` | `'update'` | 更新操作方法名称标识 | +| `accessors.delete` | `string` | `'destroy'` | 删除操作方法名称标识 | +| `accessors.add` | `string` | `'add'` | 增加关联操作方法名称标识 | +| `accessors.remove` | `string` | `'remove'` | 移除关联操作方法名称标识 | +| `accessors.set` | `string` | `'set'` | 全量设置关联操作方法名称标识 | + +**示例** + +在创建 app 时件时,可以通过 `resourcer` 选项传入: + +```ts +const app = new Application({ + // 对应默认 resourcer 实例的配置项 + resourcer: { + prefix: process.env.API_BASE_PATH + } +}); +``` + +## 实例方法 + +### `define()` + +定义并向资源管理器注册一个资源对象。通常代替 `Resource` 类的构造函数使用。 + +**签名** + +* `define(options: ResourceOptions): Resource` + +**参数** + +详见 [Resource 构造函数](/api/server/resourcer/resource#构造函数)。 + +**示例** + +```ts +app.resourcer.define({ + name: 'books', + actions: { + // 扩展的 action + publish(ctx, next) { + ctx.body = 'ok'; + } + } +}); +``` + +### `isDefined()` + +检查对应名称的资源是否已被注册。 + +**签名** + +* `isDefined(name: string): boolean` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `name` | `string` | - | 资源名称 | + +**示例** + +```ts +app.resourcer.isDefined('books'); // true +``` + +### `registerAction()` + +向资源管理器注册一个操作,可以指定针对特定的资源,如不指定资源名称,则认为是针对全局所有资源都可访问的操作。 + +**签名** + +* `registerAction(name: string, handler: HandlerType): void` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `name` | `string` | - | 操作名称 | +| `handler` | `HandlerType` | - | 操作处理函数 | + +`name` 的值如果以 `:` 开头则代表仅针对 `` 资源可访问,否则认为是全局操作。 + +**示例** + +```ts +// 注册后任意资源都可以进行 upload 操作 +app.resourcer.registerAction('upload', async (ctx, next) => { + ctx.body = 'ok'; +}); + +// 仅针对 attachments 资源注册 upload 操作 +app.resourcer.registerAction('attachments:upload', async (ctx, next) => { + ctx.body = 'ok'; +}); +``` + +### `registerActions()` + +向资源管理器注册多个操作的集合方法。 + +**签名** + +* `registerActions(actions: { [name: string]: HandlerType }): void` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `actions` | `{ [name: string]: HandlerType }` | - | 操作集合 | + +**示例** + +```ts +app.resourcer.registerActions({ + upload: async (ctx, next) => { + ctx.body = 'ok'; + }, + 'attachments:upload': async (ctx, next) => { + ctx.body = 'ok'; + } +}); +``` + +### `getResource()` + +获取对应名称的资源对象。 + +**签名** + +* `getResource(name: string): Resource` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `name` | `string` | - | 资源名称 | + +**示例** + +```ts +app.resourcer.getResource('books'); +``` + +### `getAction()` + +获取对应名称的操作处理函数。 + +**签名** + +* `getAction(name: string): HandlerType` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `name` | `string` | - | 操作名称 | + +`name` 的值如果以 `:` 开头则代表仅针对 `` 资源的操作,否则认为是全局操作。 + +**示例** + +```ts +app.resourcer.getAction('upload'); +app.resourcer.getAction('attachments:upload'); +``` + +### `use()` + +以 Koa 的形式注册一个中间件,中间件形成一个队列,并排在所有资源的操作处理函数之前执行。 + +**签名** + +* `use(middleware: Middleware | Middleware[]): void` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `middleware` | `Middleware \| Middleware[]` | - | 中间件 | + +**示例** + +```ts +app.resourcer.use(async (ctx, next) => { + console.log(ctx.req.url); + await next(); +}); +``` + +### `middleware()` + +生成一个兼容 Koa 的中间件,用于将资源的路由处理注入到应用中。 + +**签名** + +* `middleware(options: KoaMiddlewareOptions): KoaMiddleware` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options.prefix?` | `string` | `''` | 路径前缀。 | +| `options.accessors?` | `Object` | `{}` | 常用方法的名称映射,与构造函数的 `accessors` 参数结构相同。 | + +**示例** + +```ts +const koa = new Koa(); + +const resourcer = new Resourcer(); + +// 生成兼容 Koa 的中间件 +koa.use(resourcer.middleware()); +``` diff --git a/docs/tr-TR/api/resourcer/middleware.md b/docs/tr-TR/api/resourcer/middleware.md new file mode 100644 index 000000000..1f0a4d3a8 --- /dev/null +++ b/docs/tr-TR/api/resourcer/middleware.md @@ -0,0 +1,168 @@ +# Middleware + +与 Koa 的中间件类似,但提供了更多增强的功能,可以方便的进行更多的扩展。 + +中间件定义后可以在资源管理器等多处进行插入使用,由开发者自行控制调用的时机。 + +## 构造函数 + +**签名** + +* `constructor(options: Function)` +* `constructor(options: MiddlewareOptions)` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options` | `Function` | - | 中间件处理函数 | +| `options` | `MiddlewareOptions ` | - | 中间件配置项 | +| `options.only` | `string[]` | - | 仅允许指定的操作 | +| `options.except` | `string[]` | - | 排除指定的操作 | +| `options.handler` | `Function` | - | 处理函数 | + +**示例** + +简单定义: + +```ts +const middleware = new Middleware((ctx, next) => { + await next(); +}); +``` + +使用相关参数: + +```ts +const middleware = new Middleware({ + only: ['create', 'update'], + async handler(ctx, next) { + await next(); + }, +}); +``` + +## 实例方法 + +### `getHandler()` + +返回已经过编排的处理函数。 + +**示例** + +以下中间件在请求时会先输出 `1`,再输出 `2`。 + +```ts +const middleware = new Middleware((ctx, next) => { + console.log(1); + await next(); +}); + +middleware.use(async (ctx, next) => { + console.log(2); + await next(); +}); + +app.resourcer.use(middleware.getHandler()); +``` + +### `use()` + +对当前中间件添加中间件函数。用于提供中间件的扩展点。示例见 `getHandler()`。 + +**签名** + +* `use(middleware: Function)` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `middleware` | `Function` | - | 中间件处理函数 | + +### `disuse()` + +移除当前中间件已添加的中间件函数。 + +**签名** + +* `disuse(middleware: Function)` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `middleware` | `Function` | - | 中间件处理函数 | + +**示例** + +以下示例在请求处理是只输出 `1`,不执行 fn1 中的 `2` 输出。 + +```ts +const middleware = new Middleware((ctx, next) => { + console.log(1); + await next(); +}); + +async function fn1(ctx, next) { + console.log(2); + await next(); +} + +middleware.use(fn1); + +app.resourcer.use(middleware.getHandler()); + +middleware.disuse(fn1); +``` + +### `canAccess()` + +判断当前中间件针对特定操作是否要被调用,通常由资源管理器内部处理。 + +**签名** + +* `canAccess(name: string): boolean` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `name` | `string` | - | 操作名称 | + +## 其他导出 + +### `branch()` + +创建一个分支中间件,用于在中间件中进行分支处理。 + +**签名** + +* `branch(map: { [key: string]: Function }, reducer: Function, options): Function` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `map` | `{ [key: string]: Function }` | - | 分支处理函数映射表,键名由后续计算函数在调用时给出 | +| `reducer` | `(ctx) => string` | - | 计算函数,用于基于上下文计算出分支的键名 | +| `options?` | `Object` | - | 分支配置项 | +| `options.keyNotFound?` | `Function` | `ctx.throw(404)` | 未找到键名时的处理函数 | +| `options.handlerNotSet?` | `Function` | `ctx.throw(404)` | 未定义处理函数时的处理 | + +**示例** + +用户验证时,根据请求 URL 中 query 部分的 `authenticator` 参数的值决定后续需要如何处理: + +```ts +app.resourcer.use(branch({ + 'password': async (ctx, next) => { + // ... + }, + 'sms': async (ctx, next) => { + // ... + }, +}, (ctx) => { + return ctx.action.params.authenticator ?? 'password'; +})); +``` diff --git a/docs/tr-TR/api/resourcer/resource.md b/docs/tr-TR/api/resourcer/resource.md new file mode 100644 index 000000000..4e83b2d21 --- /dev/null +++ b/docs/tr-TR/api/resourcer/resource.md @@ -0,0 +1,114 @@ +# Resource + +Resource 用于定义资源实例。被 Resourcer 管理的资源实例都可以通过 HTTP 请求访问。 + +## 构造函数 + +用于创建 Resource 实例。通常由 Resourcer 管理器的 `define()` 接口调用替代,不需要直接使用。 + +**签名** + +* `constructor(options: ResourceOptions, resourcer: Resourcer)` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options.name` | `string` | - | 资源名称,对应 URL 路由中的资源地址部分。 | +| `options.type` | `string` | `'single'` | 资源类型,可选项为 `'single'`、`'hasOne'`、`'hasMany'`、`'belongsTo'`、`'belongsToMany'`。 | +| `options.actions` | `Object` | - | 对资源可进行的操作列表,详见示例部分。 | +| `options.middlewares` | `MiddlewareType \| MiddlewareType[]` | - | 对当前定义资源进行任意操作访问时的中间件列表,详见示例部分。 | +| `options.only` | `ActionName[]` | `[]` | 针对全局操作的白名单列表,当数组中有值时(`length > 0`),只有数组中的操作可被访问。 | +| `options.except` | `ActionName[]` | `[]` | 针对全局操作的黑名单列表,当数组中有值时(`length > 0`),除数组中的操作外,其他操作可被访问。 | +| `resourcer` | `Resourcer` | - | 所属资源管理器实例。 | + +**示例** + +```ts +app.resourcer.define({ + name: 'books', + actions: { + // 扩展的 action + publish(ctx, next) { + ctx.body = 'ok'; + } + }, + middleware: [ + // 扩展的中间件 + async (ctx, next) => { + await next(); + } + ] +}); +``` + +## 实例成员 + +### `options` + +当前资源的配置项。 + +### `resourcer` + +所属的资源管理器实例。 + +### `middlewares` + +已注册的中间件列表。 + +### `actions` + +已注册的操作映射表。 + +### `except` + +操作排除的名单列表。 + +## 实例方法 + +### `getName()` + +获取当前资源的名称。 + +**签名** + +* `getName(): string` + +**示例** + +```ts +const resource = app.resourcer.define({ + name: 'books' +}); + +resource.getName(); // 'books' +``` + +### `getAction()` + +根据名称获取当前资源的操作。 + +**签名** + +* `getAction(name: string): Action` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `name` | `string` | - | 操作名称。 | + +**示例** + +```ts +const resource = app.resourcer.define({ + name: 'books', + actions: { + publish(ctx, next) { + ctx.body = 'ok'; + } + } +}); + +resource.getAction('publish'); // [Function: publish] +``` diff --git a/docs/tr-TR/api/sdk.md b/docs/tr-TR/api/sdk.md new file mode 100644 index 000000000..d4fc6fa70 --- /dev/null +++ b/docs/tr-TR/api/sdk.md @@ -0,0 +1,281 @@ +# @nocobase/sdk + +## APIClient + +```ts +class APIClient { + // axios 实例 + axios: AxiosInstance; + // 构造器 + constructor(instance?: AxiosInstance | AxiosRequestConfig); + // 客户端请求,支持 AxiosRequestConfig 和 ResourceActionOptions + request, D = any>(config: AxiosRequestConfig | ResourceActionOptions): Promise; + // 获取资源 + resource(name: string, of?: any): R; +} +``` + +初始化实例 + +```ts +import axios from 'axios'; +import { APIClient } from '@nocobase/sdk'; + +// 提供 AxiosRequestConfig 配置参数 +const api = new APIClient({ + baseURL: 'https://localhost:8000/api', +}); + +// 提供 AxiosInstance +const instance = axios.create({ + baseURL: 'https://localhost:8000/api', +}); +const api = new APIClient(instance); +``` + +## Mock + +```ts +import { APIClient } from '@nocobase/sdk'; +import MockAdapter from 'axios-mock-adapter'; + +const api = new APIClient({ + baseURL: 'https://localhost:8000/api', +}); + +const mock = new MockAdapter(api.axios); + +mock.onGet('users:get').reply(200, { + data: { id: 1, name: 'John Smith' }, +}); + +await api.request({ url: 'users:get' }); +``` + +## Storage + +APIClient 默认使用的 localStorage,你也可以自定义 Storage,如: + +```ts +import { Storage } from '@nocobase/sdk'; + +class MemoryStorage extends Storage { + items = new Map(); + + clear() { + this.items.clear(); + } + + getItem(key: string) { + return this.items.get(key); + } + + setItem(key: string, value: string) { + return this.items.set(key, value); + } + + removeItem(key: string) { + return this.items.delete(key); + } +} + +const api = new APIClient({ + baseURL: 'https://localhost:8000/api', + storageClass: CustomStorage, +}); +``` + +## Auth + +```ts +// 登录并记录 token +api.auth.signIn({ email, password }); +// 注销并删除 token +api.auth.signOut(); +// 设置 token +api.auth.setToken('123'); +// 设置 role(当需要多角色时) +api.auth.setRole('admin'); +// 设置 locale(当需要多语言时) +api.auth.setLocale('zh-CN'); +``` + +自定义 Auth + +```ts +import { Auth } from '@nocobase/sdk'; + +class CustomAuth extends Auth { + +} + +const api = new APIClient({ + baseURL: 'https://localhost:8000/api', + authClass: CustomAuth, +}); +``` + +## Request + +```ts +// url +await api.request({ + url: 'users:list', + // request params + params: { + filter: { + 'email.$includes': 'noco', + }, + }, + // request body + data, +}); + +// resource & action +await api.request({ + resource: 'users', + action: 'list', + // action params + params: { + filter: { + 'email.$includes': 'noco', + }, + page: 1, + }, +}); +``` + +## Resource action + +```ts +await api.resource('collection')[action](); +await api.resource('collection.association', collectionId)[action](); +``` + +## Action API + +```ts +await api.resource('collection').create(); +await api.resource('collection').get(); +await api.resource('collection').list(); +await api.resource('collection').update(); +await api.resource('collection').destroy(); +await api.resource('collection.association', collectionId).create(); +await api.resource('collection.association', collectionId).get(); +await api.resource('collection.association', collectionId).list(); +await api.resource('collection.association', collectionId).update(); +await api.resource('collection.association', collectionId).destroy(); +``` + +### `get` + +```ts +interface Resource { + get: (options?: GetActionOptions) => Promise; +} + +interface GetActionOptions { + filter?: any; + filterByTk?: any; + fields?: string || string[]; + appends?: string || string[]; + expect?: string || string[]; + sort?: string[]; +} +``` + +### `list` + +```ts +interface Resource { + list: (options?: ListActionOptions) => Promise; +} + +interface ListActionOptions { + filter?: any; + filterByTk?: any; + fields?: string || string[]; + appends?: string || string[]; + expect?: string || string[]; + sort?: string[]; + page?: number; + pageSize?: number; + paginate?: boolean; +} +``` + +### `create` + +```ts +interface Resource { + create: (options?: CreateActionOptions) => Promise; +} + +interface CreateActionOptions { + whitelist?: string[]; + blacklist?: string[]; + values?: {[key: sting]: any}; +} +``` + +### `update` + +```ts +interface Resource { + update: (options?: UpdateActionOptions) => Promise; +} + +interface UpdateActionOptions { + filter?: any; + filterByTk?: any; + whitelist?: string[]; + blacklist?: string[]; + values?: {[key: sting]: any}; +} +``` + +### `destroy` + +```ts +interface Resource { + destroy: (options?: DestroyActionOptions) => Promise; +} + +interface DestroyActionOptions { + filter?: any; + filterByTk?: any; +} +``` + +### `move` + +```ts +interface Resource { + move: (options?: MoveActionOptions) => Promise; +} + +interface MoveActionOptions { + sourceId: any; + targetId?: any; + /** @default 'sort' */ + sortField?: any; + targetScope?: {[key: string]: any}; + sticky?: boolean; + method?: 'insertAfter' | 'prepend'; +} +``` + +### `` + +```ts +interface AttachmentResource { + +} + +interface UploadActionOptions { + +} + +api.resource('attachments').upload(); +api.resource('attachments').upload(); +``` diff --git a/docs/tr-TR/api/server/app-manager.md b/docs/tr-TR/api/server/app-manager.md new file mode 100644 index 000000000..76a11afbe --- /dev/null +++ b/docs/tr-TR/api/server/app-manager.md @@ -0,0 +1 @@ +# AppManager \ No newline at end of file diff --git a/docs/tr-TR/api/server/application.md b/docs/tr-TR/api/server/application.md new file mode 100644 index 000000000..8aac7a008 --- /dev/null +++ b/docs/tr-TR/api/server/application.md @@ -0,0 +1,188 @@ +# Application + +基于 [Koa](https://koajs.com/) 实现的 WEB 框架,兼容所有的 Koa 插件。 + +## 构造函数 + +### `constructor()` + +创建一个应用实例。 + +**签名** + +* `constructor(options: ApplicationOptions)` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options.database` | `IDatabaseOptions` or `Database` | `{}` | 数据库配置 | +| `options.resourcer` | `ResourcerOptions` | `{}` | 资源路由配置 | +| `options.cors` | [`CorsOptions`](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/koa__cors/index.d.ts#L24) | `{}` | 跨域配置,参考 [@koa/cors](https://npmjs.com/package/@koa/cors) | +| `options.dataWrapping` | `boolean` | `true` | 是否包装响应数据,`true` 则将把通常的 `ctx.body` 包装为 `{ data, meta }` 的结构。 | +| `options.registerActions` | `boolean` | `true` | 是否注册默认的 [actions](#) | +| `options.i18n` | `I18nOptions` | `{}` | 国际化配置,参考 [i18next](https://www.i18next.com/overview/api) | +| `options.plugins` | `PluginConfiguration[]` | `[]` | 默认启用的插件配置 | + +Type + +```ts +interface ApplicationOptions { + +} +``` + +**示例** + +```ts +import Application from '@nocobase/server'; + +const app = new Application({ + database: { + dialect: 'mysql', + host: 'localhost', + port: 3306, + username: 'root', + password: '123456', + database: 'test', + }, + resourcer: { + prefix: '/api', + }, + cors: { + origin: '*', + } +}); +``` + +## 实例成员 + +### `cli` + +命令行工具实例,参考 npm 包 [Commander](https://www.npmjs.com/package/commander)。 + +### `db` + +数据库实例,相关 API 参考 [Database](/api/database)。 + +### `resourcer` + +应用初始化自动创建的资源路由管理实例,相关 API 参考 [Resourcer](/api/resourcer)。 + +### `acl` + +ACL 实例,相关 API 参考 [ACL](/api/acl)。 + +### `i18n` + +I18next 实例,相关 API 参考 [I18next](https://www.i18next.com/overview/api)。 + +### `pm` + +插件管理器实例,相关 API 参考 [PluginManager](./plugin-manager)。 + +### `version` + +应用版本实例,相关 API 参考 [ApplicationVersion](./application-version)。 + +### `middleware` + +内置的中间件有: + +- i18next +- bodyParser +- cors +- dataWrapping +- collection2resource +- restApiMiddleware + +### `context` + +继承自 koa 的 context,可以通过 `app.context` 访问,用于向每个请求注入上下文可访问的内容。参考 [Koa Context](https://koajs.com/#app-context)。 + +NocoBase 默认对 context 注入了以下成员,可以在请求处理函数中直接使用: + +| 变量名 | 类型 | 描述 | +| --- | --- | --- | +| `ctx.app` | `Application` | 应用实例 | +| `ctx.db` | `Database` | 数据库实例 | +| `ctx.resourcer` | `Resourcer` | 资源路由管理器实例 | +| `ctx.action` | `Action` | 资源操作相关对象实例 | +| `ctx.i18n` | `I18n` | 国际化实例 | +| `ctx.t` | `i18n.t` | 国际化翻译函数快捷方式 | +| `ctx.getBearerToken()` | `Function` | 获取请求头中的 bearer token | + +## 实例方法 + +### `use()` + +注册中间件,兼容所有 [Koa 插件](https://www.npmjs.com/search?q=koa) + +### `on()` + +订阅应用级事件,主要与生命周期相关,等同于 `eventEmitter.on()`。所有可订阅事件参考 [事件](#事件)。 + +### `command()` + +自定义 command + +### `findCommand()` + +查找已定义 command + +### `runAsCLI()` + +以 CLI 的方式运行。 + +### `load()` + +加载应用配置。 + +**签名** + +* `async load(): Promise` + +### `reload()` + +重载应用配置。 + +### `install()` + +初始化安装应用,同步安装插件。 + +### `upgrade()` + +升级应用,同步升级插件。 + +### `start()` + +启动应用,如果配置了监听的端口,将启动监听,之后应用即可接受 HTTP 请求。 + +**签名** + +* `async start(options: StartOptions): Promise` + +**参数** + +| 参数名 | 类型 | 默认值 | 描述 | +| --- | --- | --- | --- | +| `options.listen?` | `ListenOptions` | `{}` | HTTP 监听参数对象 | +| `options.listen.port?` | `number` | 13000 | 端口 | +| `options.listen.host?` | `string` | `'localhost'` | 域名 | + +### `stop()` + +停止应用,此方法会关闭数据库连接,关闭 HTTP 端口,不会删除数据。 + +### `destroy()` + +删除应用,此方法会删除应用对应的数据库。 + +## 事件 + +### `'beforeLoad'` / `'afterLoad'` +### `'beforeInstall'` / `'afterInstall'` +### `'beforeUpgrade'` / `'afterUpgrade'` +### `'beforeStart'` / `'afterStart'` +### `'beforeStop'` / `'afterStop'` +### `'beforeDestroy'` / `'afterDestroy'` diff --git a/docs/tr-TR/api/server/i18n.md b/docs/tr-TR/api/server/i18n.md new file mode 100644 index 000000000..05b5b6193 --- /dev/null +++ b/docs/tr-TR/api/server/i18n.md @@ -0,0 +1 @@ +# I18n diff --git a/docs/tr-TR/api/server/plugin-manager.md b/docs/tr-TR/api/server/plugin-manager.md new file mode 100644 index 000000000..e161d37f0 --- /dev/null +++ b/docs/tr-TR/api/server/plugin-manager.md @@ -0,0 +1,59 @@ +# PluginManager + +应用插件管理器的实例,由应用自动创建,可以通过 `app.pm` 访问。 + +## 实例方法 + +### `create()` + +在本地创建一个插件脚手架 + +**签名** + +```ts +create(name, options): void; +``` + +### `addStatic()` + +**签名** + +```ts +addStatic(plugin: any, options?: PluginOptions): Plugin; +``` + +**示例** + +```ts +pm.addStatic('nocobase'); +``` + +### `add()` + +**签名** + +```ts +async add(plugin: any, options?: PluginOptions): Promise; +async add(plugin: string[], options?: PluginOptions): Promise; +``` + +**示例** + +```ts +await pm.add(['test'], { + builtIn: true, + enabled: true, +}); +``` + +### `get()` + +获取插件实例 + +### `enable()` + +### `disable()` + +### `remove()` + +### `upgrade()` diff --git a/docs/tr-TR/api/server/plugin.md b/docs/tr-TR/api/server/plugin.md new file mode 100644 index 000000000..2f7a9705c --- /dev/null +++ b/docs/tr-TR/api/server/plugin.md @@ -0,0 +1,59 @@ +# Plugin + +## 示例 + +```ts +const app = new Application(); + +class MyPlugin extends Plugin { + afterAdd() {} + beforeLoad() {} + load() {} + install() {} + afterEnable() {} + afterDisable() {} + remove() {} +} + +app.plugin(MyPlugin, { name: 'my-plugin' }); +``` + +## 属性 + +### `options` + +插件配置信息 + +### `name` + +插件标识,只读 + +## 实例方法 + +### `afterAdd()` + +插件 add/addStatic 之后 + +### `beforeLoad()` + +插件加载前,如事件或类注册 + +### `load()` + +加载插件,配置之类 + +### `install()` + +插件安装逻辑,如初始化数据 + +### `afterEnable()` + +插件激活之后的逻辑 + +### `afterDisable()` + +插件禁用之后的逻辑 + +### `remove()` + +用于实现插件删除逻辑 \ No newline at end of file diff --git a/docs/tr-TR/development/app-ds.md b/docs/tr-TR/development/app-ds.md new file mode 100644 index 000000000..b75ecf808 --- /dev/null +++ b/docs/tr-TR/development/app-ds.md @@ -0,0 +1,64 @@ +# App directory structure + +Either [Git source](/welcome/getting-started/installation/git-clone) or [create-nocobase-app](/welcome/getting-started/installation/create-nocobase-app), the directory structure of the created NocoBase application is the same, with the following structure. + +```bash +├── my-nocobase-app + ├── packages # NocoBase uses the Monorepo approach to manage code, dividing different modules into different packages + ├── app + ├── client # client-side module + ├── server # server-side modules + ├─ plugins # plugin directory + ├── storage # for database files, attachments, cache, etc. + ├─ db + ├── .env # environment variables + ├── .buildrc.ts # package configuration for packages, supports cjs, esm and umd packages. + ├── jest.config.js + ├── jest.setup.ts + ├── lerna.json + ├── package.json + ├── tsconfig.jest.json + ├─ tsconfig.json + ├── tsconfig.server.json +``` + +## Packages directory + +```bash +├─ packages + ├─ app + ├── client + ├─ public + ├─ src + ├─ pages + ├─ index.tsx + ├─ .umirc.ts + ├─ package.json + ├─ server + ├─ src + ├─ config + ├─ index.ts + ├─ package.json + ├─ /plugins + ├─ my-plugin + ├─ src + ├─ package.json +``` + +NocoBase uses the Monorepo approach to manage code, dividing different modules into different packages. + +- `app/client` is the client module of the application, built on [umi](https://umijs.org/zh-CN). +- `app/server` is the server-side module of the application. +- The `plugins/*` directory can hold various plugins. + +## storages directory + +Store database files, attachments, cache, etc. + +## .env files + +Environment variables. + +## .buildrc.ts file + +Package configuration for packages, supporting cjs, esm and umd formats. diff --git a/docs/tr-TR/development/client/i18n.md b/docs/tr-TR/development/client/i18n.md new file mode 100644 index 000000000..49136d010 --- /dev/null +++ b/docs/tr-TR/development/client/i18n.md @@ -0,0 +1,140 @@ +# Internationalization + +Client-side internationalization for multiple languages is implemented based on the npm package [react-i18next](https://npmjs.com/package/react-i18next), which provides a wrapper for the `` component at the top level of the application, allowing the relevant methods to be used directly at any location. + +Adding language packages: + +```tsx | pure +import { i18n } from '@nocobase/client'; + +i18n.addResources('zh-CN', 'test', { + Hello: '你好', + World: '世界', +}); +``` + +Note: Here the second parameter filled in `'test'` is the language namespace, usually the plugin itself defines the language resources should create a specific namespace according to their own plugin package name, in order to avoid conflicts with other language resources. The default namespace in NocoBase is `'client'` and most common and basic language translations are placed in this namespace. When the required language is not provided, it can be defined by extension in the plugin's own namespace. + +To call the translation function in the component: + +```tsx | pure +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +export default function MyComponent() { + // Use the previously defined namespace + const { t } = useTranslation('test'); + + return ( +
+

{t('World')}

+
+ ); +} +``` + +The template method `'{{t()}}'` can be used directly in the SchemaComponent component, and the translation functions in the template will automatically be executed. + +```tsx | pure +import React from 'react'; +import { SchemaComponent } from '@nocobase/client'; + +export default function MySchemaComponent() { + return ( + + ); +} +``` + +In some special cases where it is also necessary to define multilingualism as a template, the NocoBase built-in `compile()` method can be used to compile to multilingual results. + +```tsx | pure +import React from 'react'; +import { useCompile } from '@nocobase/client'; + +const title = '{{t("Hello", { ns: "test" })}}'; + +export default function MyComponent() { + const { compile } = useCompile(); + + return ( +
{compile(title)}
+ ); +} +``` + +## Suggested configuration + +With English text as the key and translation as the value, the benefit of this, even if multiple languages are missing, it will be displayed in English and will not cause reading barriers, e.g. + +```ts +i18n.addResources('zh-CN', 'my-plugin', { + 'Show dialog': '显示对话框', + 'Hide dialog': '隐藏对话框' +}); +``` + +To make it easier to manage multilingual files, it is recommended to create a `locale` folder in the plugin and place all the corresponding language files in it for easy management. + +```bash +|- /my-plugin + |- /src + |- /client + |- locale # Multilingual folder + |- zh-CN.ts + |- en-US.ts +``` + +## Example + +### Client-side components with multiple languages + +For example, the order status component, with different text displays depending on the value. + +```tsx | pure +import React from 'react'; +import { Select } from 'antd'; +import { i18n } from '@nocobase/client'; +import { useTranslation } from 'react-i18next'; + +i18n.addResources('zh-CN', 'sample-shop-i18n', { + Pending: '已下单', + Paid: '已支付', + Delivered: '已发货', + Received: '已签收' +}); + +const ORDER_STATUS_LIST = [ + { value: -1, label: 'Canceled (untranslated)' }, + { value: 0, label: 'Pending' }, + { value: 1, label: 'Paid' }, + { value: 2, label: 'Delivered' }, + { value: 3, label: 'Received' }, +] + +function OrderStatusSelect() { + const { t } = useTranslation('sample-shop-i18n'); + + return ( + + ); +} + +export default function () { + return ( + + ); +} +``` diff --git a/docs/tr-TR/development/client/index.md b/docs/tr-TR/development/client/index.md new file mode 100644 index 000000000..83983e138 --- /dev/null +++ b/docs/tr-TR/development/client/index.md @@ -0,0 +1,73 @@ +# Overview + +Most of the extensions for the NocoBase client are provided as Providers. + +## Built-in Providers + +- APIClientProvider +- I18nextProvider +- AntdConfigProvider +- RemoteRouteSwitchProvider +- SystemSettingsProvider +- PluginManagerProvider +- SchemaComponentProvider +- SchemaInitializerProvider +- BlockSchemaComponentProvider +- AntdSchemaComponentProvider +- DocumentTitleProvider +- ACLProvider + +## Registration of client-side Provider modules + +Static Providers are registered with app.use() and dynamic Providers are adapted with dynamicImport. + +```tsx | pure +import React from 'react'; +import { Application } from '@nocobase/client'; + +const app = new Application({ + apiClient: { + baseURL: process.env.API_BASE_URL, + }, + dynamicImport: (name: string) => { + return import(`... /plugins/${name}`); + }, +}); + +// When visiting the /hello page, display Hello world! +const HelloProvider = React.memo((props) => { + const location = useLocation(); + if (location.pathname === '/hello') { + return
Hello world!
+ } + return <>{props.children} +}); + +app.use(HelloProvider); +``` + +## Client-side of plugins + +Directory structure of the client-side of an empty plugin is as follows + +```bash +|- /my-plugin + |- /src + |- /client + |- index.tsx + |- client.d.ts + |- client.js +``` + +``client/index.tsx`` reads as follows. + +```tsx | pure +import React from 'react'; + +// This is an empty Provider, only children are passed, no custom Context is provided +export default React.memo((props) => { + return <>{props.children}; +}); +``` + +After the plugin pm.add, it writes the `my-plugin.ts` file to the `packages/app/client/src/plugins` directory. diff --git a/docs/tr-TR/development/client/m.svg b/docs/tr-TR/development/client/m.svg new file mode 100644 index 000000000..932d4de2b --- /dev/null +++ b/docs/tr-TR/development/client/m.svg @@ -0,0 +1 @@ +
Pylons Application
restApi
action
acl
parseToken
db2resource
dataWrapping
Before
After
Request
Response
before
after
resourcer.use
checkRole
app.use
\ No newline at end of file diff --git a/docs/tr-TR/development/client/settings-center.md b/docs/tr-TR/development/client/settings-center.md new file mode 100644 index 000000000..848bb7dd7 --- /dev/null +++ b/docs/tr-TR/development/client/settings-center.md @@ -0,0 +1,33 @@ +# Settings Center + + + +## Example + +```tsx | pure +import { SettingsCenterProvider } from '@nocobase/client'; +import React, { useContext } from 'react'; + +const HelloTab => () =>
Hello Tab
; + +export default React.memo((props) => { + return ( + {props.children} + ); +}); +``` + +See [samples/hello](https://github.com/nocobase/nocobase/tree/develop/packages/samples/hello) for full examples. diff --git a/docs/tr-TR/development/client/settings-center/settings-tab.jpg b/docs/tr-TR/development/client/settings-center/settings-tab.jpg new file mode 100644 index 000000000..724de7bfa Binary files /dev/null and b/docs/tr-TR/development/client/settings-center/settings-tab.jpg differ diff --git a/docs/tr-TR/development/client/test.md b/docs/tr-TR/development/client/test.md new file mode 100644 index 000000000..d8d19e163 --- /dev/null +++ b/docs/tr-TR/development/client/test.md @@ -0,0 +1,41 @@ +# Testing + +Testing is based on the [Jest](https://jestjs.io/) testing framework. Also included are common React testing libraries such as [@testing-library/react](https://testing-library.com/docs/react-testing-library/intro/) + +## Example + +```tsx | pure +import { render } from '@testing-library/react'; +import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import { RouteSwitch } from '../RouteSwitch'; +import { RouteSwitchProvider } from '../RouteSwitchProvider'; + +const Home = () =>

Home

; +const About = () =>

About

; + +describe('route-switch', () => { + it('case 1', () => { + const App = () => { + return ( + + + + + + ); + }; + const { container } = render(); + expect(container).toMatchSnapshot(); + }); +}); +``` diff --git a/docs/tr-TR/development/client/ui-router.md b/docs/tr-TR/development/client/ui-router.md new file mode 100644 index 000000000..c167293cd --- /dev/null +++ b/docs/tr-TR/development/client/ui-router.md @@ -0,0 +1,63 @@ +# UI Routing + +NocoBase Client's Router is based on [React Router](https://v5.reactrouter.com/web/guides/quick-start) and can be configured via `` to configure ui routes with the following example. + +```tsx +/** + * defaultShowCode: true + */ +import React from 'react'; +import { Link, MemoryRouter as Router } from 'react-router-dom'; +import { RouteRedirectProps, RouteSwitchProvider, RouteSwitch } from '@nocobase/client'; + +const Home = () =>

Home

; +const About = () =>

About

; + +const routes: RouteRedirectProps[] = [ + { + type: 'route', + path: '/', + exact: true, + component: 'Home', + }, + { + type: 'route', + path: '/about', + component: 'About', + }, +]; + +export default () => { + return ( + + + Home, About + + + + ); +}; +``` + +In a full NocoBase application, the Route can be extended in a similar way as follows. + +```tsx | pure +import { RouteSwitchContext } from '@nocobase/client'; +import React, { useContext } from 'react'; + +const HelloWorld = () => { + return
Hello ui router
; +}; + +export default React.memo((props) => { + const ctx = useContext(RouteSwitchContext); + ctx.routes.push({ + type: 'route', + path: '/hello-world', + component: HelloWorld, + }); + return {props.children}; +}); +``` + +See [packages/samples/custom-page](https://github.com/nocobase/nocobase/tree/develop/packages/samples/custom-page) for the full example diff --git a/docs/tr-TR/development/client/ui-schema-designer/acl.md b/docs/tr-TR/development/client/ui-schema-designer/acl.md new file mode 100644 index 000000000..d1c6127f6 --- /dev/null +++ b/docs/tr-TR/development/client/ui-schema-designer/acl.md @@ -0,0 +1,2 @@ +# ACL + diff --git a/docs/tr-TR/development/client/ui-schema-designer/block-provider.md b/docs/tr-TR/development/client/ui-schema-designer/block-provider.md new file mode 100644 index 000000000..42fa25421 --- /dev/null +++ b/docs/tr-TR/development/client/ui-schema-designer/block-provider.md @@ -0,0 +1,2 @@ +# 区块 + diff --git a/docs/tr-TR/development/client/ui-schema-designer/collection-manager.md b/docs/tr-TR/development/client/ui-schema-designer/collection-manager.md new file mode 100644 index 000000000..ff813ad87 --- /dev/null +++ b/docs/tr-TR/development/client/ui-schema-designer/collection-manager.md @@ -0,0 +1,128 @@ +# Collection Manager + +```tsx +import React from 'react'; +import { observer, ISchema, useForm } from '@formily/react'; +import { + SchemaComponent, + SchemaComponentProvider, + Form, + Action, + CollectionProvider, + CollectionField, +} from '@nocobase/client'; +import 'antd/dist/antd.css'; +import { FormItem, Input } from '@formily/antd'; + +export default observer(() => { + const collection = { + name: 'tests', + fields: [ + { + type: 'string', + name: 'title1', + interface: 'input', + uiSchema: { + title: 'Title1', + type: 'string', + 'x-component': 'Input', + required: true, + description: 'description1', + } as ISchema, + }, + { + type: 'string', + name: 'title2', + interface: 'input', + uiSchema: { + title: 'Title2', + type: 'string', + 'x-component': 'Input', + description: 'description', + default: 'ttt', + }, + }, + { + type: 'string', + name: 'title3', + }, + ], + }; + + const schema: ISchema = { + type: 'object', + properties: { + form1: { + type: 'void', + 'x-component': 'Form', + properties: { + // 字段 title1 直接使用全局提供的 uiSchema + title1: { + 'x-component': 'CollectionField', + 'x-decorator': 'FormItem', + default: '111', + }, + // 等同于 + // title1: { + // type: 'string', + // title: 'Title', + // required: true, + // 'x-component': 'Input', + // 'x-decorator': 'FormItem', + // }, + title2: { + 'x-component': 'CollectionField', + 'x-decorator': 'FormItem', + title: 'Title4', // 覆盖全局已定义的 Title2 + required: true, // 扩展的配置参数 + description: 'description4', + }, + // 等同于 + // title2: { + // type: 'string', + // title: 'Title22', + // required: true, + // 'x-component': 'Input', + // 'x-decorator': 'FormItem', + // }, + // 字段 title3 没有提供 uiSchema,自行处理 + title3: { + 'x-component': 'Input', + 'x-decorator': 'FormItem', + title: 'Title3', + required: true, + }, + action1: { + // type: 'void', + 'x-component': 'Action', + title: 'Submit', + 'x-component-props': { + type: 'primary', + useAction: '{{ useSubmit }}', + }, + }, + }, + }, + }, + }; + + const useSubmit = () => { + const form = useForm(); + return { + async run() { + form.submit(() => { + console.log(form.values); + }); + }, + }; + }; + + return ( + + + + + + ); +}); +``` \ No newline at end of file diff --git a/docs/tr-TR/development/client/ui-schema-designer/component-library.md b/docs/tr-TR/development/client/ui-schema-designer/component-library.md new file mode 100644 index 000000000..db99b2c6b --- /dev/null +++ b/docs/tr-TR/development/client/ui-schema-designer/component-library.md @@ -0,0 +1,82 @@ +# Schema 组件库 + +## 包装器组件 + +- BlockItem +- FormItem +- CardItem + +## 布局 + +- Page +- Grid +- Tabs +- Space + +## 字段组件 + +字段组件一般不单独使用,而是用在数据展示组件当中 + +- CollectionField:万能组件 +- Cascader +- Checkbox +- ColorSelect +- DatePicker +- Filter +- Formula +- IconPicker +- Input +- InputNumber +- Markdown +- Password +- Percent +- Radio +- RecordPicker +- RichText +- Select +- TimePicker +- TreeSelect +- Upload + +## 数据展示组件 + +需要与字段组件搭配使用 + +- Calendar +- Form +- Kanban +- Table +- TableV2 + +## 操作(onClick 事件型组件) + +- Action +- Action.Drawer +- Action.Modal +- ActionBar:用于操作布局 +- Menu + +## 其他 + +- G2plot +- Markdown.Void + +## `x-designer` 和 `x-initializer` 的使用场景 + +`x-decorator` 或 `x-component` 是以下组件时,`x-designer` 生效: + +- BlockItem +- CardItem +- FormItem +- Table.Column +- Tabs.TabPane + +`x-decorator` 或 `x-component` 是以下组件时,`x-initializer` 生效: + +- ActionBar +- BlockItem +- CardItem +- FormItem +- Grid +- Table +- Tabs \ No newline at end of file diff --git a/docs/tr-TR/development/client/ui-schema-designer/demo1.tsx b/docs/tr-TR/development/client/ui-schema-designer/demo1.tsx new file mode 100644 index 000000000..25469fab9 --- /dev/null +++ b/docs/tr-TR/development/client/ui-schema-designer/demo1.tsx @@ -0,0 +1,107 @@ +import { ArrayField } from '@formily/core'; +import { connect, ISchema, observer, RecursionField, useField, useFieldSchema } from '@formily/react'; +import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; +import { Table, TableColumnType } from 'antd'; +import React from 'react'; + +const ArrayTable = observer((props: any) => { + const { rowKey } = props; + const field = useField(); + const schema = useFieldSchema(); + const columnSchemas = schema.reduceProperties((buf, s) => { + if (s['x-component'] === 'ArrayTable.Column') { + buf.push(s); + } + return buf; + }, []); + + const columns = columnSchemas.map((s) => { + return { + render: (value, record) => { + return ; + }, + } as TableColumnType; + }); + + return ; +}); + +const Value = connect((props) => { + return
  • value: {props.value}
  • ; +}); + +const schema: ISchema = { + type: 'object', + properties: { + objArr: { + type: 'array', + default: [ + { __path: '0', id: 1, value: 't1' }, + { + __path: '1', + id: 2, + value: 't2', + children: [ + { + __path: '1.children.0', + id: 5, + value: 't5', + parentId: 2, + }, + ], + }, + { + __path: '2', + id: 3, + value: 't3', + children: [ + { + __path: '2.children.0', + id: 4, + value: 't4', + parentId: 3, + children: [ + { + __path: '2.children.0.children.0', + id: 6, + value: 't6', + parentId: 4, + }, + { + __path: '2.children.0.children.1', + id: 7, + value: 't7', + parentId: 4, + }, + ], + }, + ], + }, + ], + 'x-component': 'ArrayTable', + 'x-component-props': { + rowKey: 'id', + }, + properties: { + c1: { + type: 'void', + 'x-component': 'ArrayTable.Column', + properties: { + value: { + type: 'string', + 'x-component': 'Value', + }, + }, + }, + }, + }, + }, +}; + +export default () => { + return ( + + + + ); +}; diff --git a/docs/tr-TR/development/client/ui-schema-designer/designable.md b/docs/tr-TR/development/client/ui-schema-designer/designable.md new file mode 100644 index 000000000..2aaa16b4a --- /dev/null +++ b/docs/tr-TR/development/client/ui-schema-designer/designable.md @@ -0,0 +1,353 @@ +# Schema 的设计能力 + +Schema 的设计能力主要体现在 + +- 邻近位置插入,可用于 + - 插入新的 schema 节点 + - 现有 schema 节点的拖拽移动 +- schema 参数修改 + +设计器核心 API 和参数有: + +- 设计器 API:`createDesignable()` & `useDesignable()` +- Schema 参数:`x-designer`,用于适配设计器组件 + +## 设计器 API + +### createDesignable() + +```ts +import { Schema } from '@nocobase/client'; + +const current = new Schema({ + type: 'void', + 'x-component': 'div', +}); + +const { + designable, // 是否可以配置 + remove, + insertAdjacent, // 在某位置插入,四个位置:beforeBegin、afterBegin、beforeEnd、afterEnd + insertBeforeBegin, // 在当前节点的前面插入 + insertAfterBegin, // 在当前节点的第一个子节点前面插入 + insertBeforeEnd, // 在当前节点的最后一个子节点后面 + insertAfterEnd, // 在当前节点的后面 +} = createDesignable({ + current, +}); + +const newSchema = { + type: 'void', + name: 'hello', + 'x-component': 'Hello', +}; + +insertAfterBegin(newSchema); + +console.log(current.toJSON()); +{ + type: 'void', + 'x-component': 'div', + properties: { + hello: { + type: 'void', + 'x-component': 'Hello', + }, + }, +} +``` + +### useDesignable() + +React Hook 场景也可以用 `useDesignable()` 获取当前 schema 组件设计器的 API + +```ts +const { + designable, // 是否可以配置 + remove, + insertAdjacent, // 在某位置插入,四个位置:beforeBegin、afterBegin、beforeEnd、afterEnd + insertBeforeBegin, // 在当前节点的前面插入 + insertAfterBegin, // 在当前节点的第一个子节点前面插入 + insertBeforeEnd, // 在当前节点的最后一个子节点后面 + insertAfterEnd, // 在当前节点的后面 +} = useDesignable(); + +const schema = { + name: uid(), + 'x-component': 'Hello', +}; + +// 在当前节点的前面插入 +insertBeforeBegin(schema); +// 等同于 +insertAdjacent('beforeBegin', schema); + +// 在当前节点的第一个子节点前面插入 +insertAfterBegin(schema); +// 等同于 +insertAdjacent('afterBegin', schema); + +// 在当前节点的最后一个子节点后面 +insertBeforeEnd(schema); +// 等同于 +insertAdjacent('beforeEnd', schema); + +// 在当前节点的后面 +insertAfterEnd(schema); +// 等同于 +insertAdjacent('afterEnd', schema); +``` + +## 邻近位置插入 + +与 DOM 的 [insert adjacent](https://dom.spec.whatwg.org/#insert-adjacent) 概念相似,Schema 也提供了 `insertAdjacent()` 方法用于解决邻近位置的插入问题。 + +四个邻近位置 + +```ts +{ + properties: { + // beforeBegin 在当前节点的前面插入 + node1: { + properties: { + // afterBegin 在当前节点的第一个子节点前面插入 + // ... + // beforeEnd 在当前节点的最后一个子节点后面 + }, + }, + // afterEnd 在当前节点的后面 + }, +} +``` + +和 HTML 标签一样,Schema 组件库的组件也是可以相互组合,通过 insertAdjacent API 按实际需要插入在合理的邻近位置。 + +### 插入新的 schema 节点 + +在 Schema 组件里,可以直接通过 `useDesignable()` 在当前 Schema 的相邻位置插入新节点: + + +示例 + +```tsx +import React from 'react'; +import { SchemaComponentProvider, SchemaComponent, useDesignable } from '@nocobase/client'; +import { observer, Schema, useFieldSchema } from '@formily/react'; +import { Button, Space } from 'antd'; +import { uid } from '@formily/shared'; + +const Hello = observer((props) => { + const { insertAdjacent } = useDesignable(); + const fieldSchema = useFieldSchema(); + return ( +
    +

    {fieldSchema.name}

    + + + + + + +
    {props.children}
    +
    + ); +}); + +const Page = observer((props) => { + return
    {props.children}
    ; +}); + +export default () => { + return ( + + + + ); +} +``` + +### 现有 schema 节点的拖拽移动 + +insertAdjacent 等方法也可用于节点的拖拽移动 + +```tsx +import React from 'react'; +import { uid } from '@formily/shared'; +import { observer, useField, useFieldSchema } from '@formily/react'; +import { DndContext, DragEndEvent, useDraggable, useDroppable } from '@dnd-kit/core'; +import { SchemaComponent, SchemaComponentProvider, createDesignable, useDesignable } from '@nocobase/client'; + +const useDragEnd = () => { + const { refresh } = useDesignable(); + + return ({ active, over }: DragEndEvent) => { + const activeSchema = active?.data?.current?.schema; + const overSchema = over?.data?.current?.schema; + + if (!activeSchema || !overSchema) { + return; + } + + const dn = createDesignable({ + current: overSchema, + }); + + dn.on('insertAdjacent', refresh); + dn.insertBeforeBeginOrAfterEnd(activeSchema); + }; +}; + +const Page = observer((props) => { + return {props.children}; +}); + +function Draggable(props) { + const { attributes, listeners, setNodeRef, transform } = useDraggable({ + id: props.id, + data: props.data, + }); + const style = transform + ? { + transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`, + } + : undefined; + + return ( + + ); +} + +function Droppable(props) { + const { isOver, setNodeRef } = useDroppable({ + id: props.id, + data: props.data, + }); + const style = { + color: isOver ? 'green' : undefined, + }; + + return ( +
    + {props.children} +
    + ); +} + +const Block = observer((props) => { + const field = useField(); + const fieldSchema = useFieldSchema(); + return ( + +
    + Block {fieldSchema.name}{' '} + + Drag + +
    +
    + ); +}); + +export default function App() { + return ( + + + + ); +} +``` + +## `x-designer` 的应用 + +`x-designer` 通常只在 BlockItem、CardItem、FormItem 等包装器组件中使用。 + +```ts +{ + type: 'object', + properties: { + title: { + type: 'string', + title: '标题', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-designer': 'FormItem.Designer', + }, + status: { + type: 'string', + title: '状态', + 'x-decorator': 'FormItem', + 'x-component': 'Select', + 'x-designer': 'FormItem.Designer', + }, + }, +} +``` + +说明:NocoBase 提供的 Schema 设计器是以工具栏形式直接嵌入于界面,当激活界面配置时(`designable = true`),`x-designer` 组件(设计器工具栏)会显示出来,就可以通过工具栏更新当前 schema 组件了,工具栏提供的设计能力包括: + +- 拖拽移动:DndContext + DragHandler +- 插入新节点:SchemaInitializer +- 参数配置:SchemaSettings diff --git a/docs/tr-TR/development/client/ui-schema-designer/designable.png b/docs/tr-TR/development/client/ui-schema-designer/designable.png new file mode 100644 index 000000000..c0261e74e Binary files /dev/null and b/docs/tr-TR/development/client/ui-schema-designer/designable.png differ diff --git a/docs/tr-TR/development/client/ui-schema-designer/extending-schema-components.md b/docs/tr-TR/development/client/ui-schema-designer/extending-schema-components.md new file mode 100644 index 000000000..5fc966819 --- /dev/null +++ b/docs/tr-TR/development/client/ui-schema-designer/extending-schema-components.md @@ -0,0 +1,518 @@ +# 扩展 Schema 组件 + +除了原生的 html 标签,开发也可以适配更多的自定义组件,用于丰富 Schema 组件库。 + +扩展组件时,常用的方法: + +- [connect](https://react.formilyjs.org/api/shared/connect) 无侵入接入第三方组件,一般用于适配字段组件,和 [mapProps](https://react.formilyjs.org/api/shared/map-props)[、mapReadPretty](https://react.formilyjs.org/api/shared/map-read-pretty) 搭配使用 +- [observer](https://react.formilyjs.org/api/shared/observer) 当组件内部使用了 observable 对象,而你希望组件响应 observable 对象的变化时 + +## 最简单的扩展 + +直接将现成的 React 组件注册进来。 + +```tsx +/** + * defaultShowCode: true + */ +import React from 'react'; +import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; + +const Hello = () =>

    Hello, world!

    ; + +const schema = { + type: 'void', + name: 'hello', + 'x-component': 'Hello', +}; + +export default () => { + return ( + + + + ); +}; +``` + +## 通过 connect 接入第三方组件 + +```tsx +/** + * defaultShowCode: true + */ +import React from 'react'; +import { Input } from 'antd' +import { connect, mapProps, mapReadPretty } from '@formily/react'; +import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; + +const ReadPretty = (props) => { + return
    {props.value}
    +}; + +const SingleText = connect( + Input, + mapProps((props, field) => { + return { + ...props, + suffix: '后缀', + } + }), + mapReadPretty(ReadPretty), +); + +const schema = { + type: 'object', + properties: { + t1: { + type: 'string', + default: 'hello t1', + 'x-component': 'SingleText', + }, + t2: { + type: 'string', + default: 'hello t2', + 'x-component': 'SingleText', + 'x-pattern': 'readPretty', + }, + } +}; + +export default () => { + return ( + + + + ); +}; +``` + +## 使用 observer 响应数据 + +```tsx +/** + * defaultShowCode: true + */ +import React from 'react'; +import { Input } from 'antd'; +import { connect, observer, useForm } from '@formily/react'; +import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; + +const SingleText = connect(Input); + +const UsedObserver = observer((props) => { + const form = useForm(); + return
    UsedObserver: {form.values.t1}
    +}); + +const NotUsedObserver = (props) => { + const form = useForm(); + return
    NotUsedObserver: {form.values.t1}
    +}; + +const schema = { + type: 'object', + properties: { + t1: { + type: 'string', + 'x-component': 'SingleText', + }, + t2: { + type: 'string', + 'x-component': 'UsedObserver', + }, + t3: { + type: 'string', + 'x-component': 'NotUsedObserver', + }, + } +}; + +const components = { + SingleText, + UsedObserver, + NotUsedObserver +}; + +export default () => { + return ( + + + + ); +}; +``` + +## 嵌套的 Schema + +- `props.children` 嵌套,适用于 void 和 object 类型的 properties,例子见 [void 和 object 类型 schema 的嵌套](#void-和-object-类型-schema-的嵌套) +- `` 自定义嵌套,所有类型都适用,例子见 [array 类型 schema 的嵌套](#array-类型-schema-的嵌套) + +注意: + +- 除了 void 和 object 类型以外的 schema 的 `properties` 无法直接通过 `props.children` 渲染,但是可以使用 `` 解决嵌套问题 +- 仅 void 和 object 类型的 schema 可以与 onlyRenderProperties 使用 + +```tsx | pure + +``` + +### void 和 object 类型 schema 的嵌套 + +直接通过 props.children 就可以适配 properties 节点了 + +```tsx +/** + * defaultShowCode: true + */ +import React from 'react'; +import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; + +// Hello 组件适配了 children,可以嵌套 properties 了 +const Hello = (props) =>

    Hello, {props.children}!

    ; +const World = () => world; + +const schema = { + type: 'object', + name: 'hello', + 'x-component': 'Hello', + properties: { + world: { + type: 'string', + 'x-component': 'World', + }, + }, +}; + +export default () => { + return ( + + + + ); +}; +``` + +各类型 properties 渲染结果对比 + +```tsx +import React from 'react'; +import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; + +const Hello = (props) =>

    Hello, {props.children}!

    ; +const World = () => world; + +const schema = { + type: 'object', + properties: { + title1: { + type: 'void', + 'x-content': 'Void schema,渲染 properties', + }, + void: { + type: 'void', + name: 'hello', + 'x-component': 'Hello', + properties: { + world: { + type: 'void', + 'x-component': 'World', + }, + }, + }, + title2: { + type: 'void', + 'x-content': 'Object schema,渲染 properties', + }, + object: { + type: 'object', + name: 'hello', + 'x-component': 'Hello', + properties: { + world: { + type: 'string', + 'x-component': 'World', + }, + }, + }, + title3: { + type: 'void', + 'x-content': 'Array schema,不渲染 properties', + }, + array: { + type: 'array', + name: 'hello', + 'x-component': 'Hello', + properties: { + world: { + type: 'string', + 'x-component': 'World', + }, + }, + }, + title4: { + type: 'void', + 'x-content': 'String schema,不渲染 properties', + }, + string: { + type: 'string', + name: 'hello', + 'x-component': 'Hello', + properties: { + world: { + type: 'string', + 'x-component': 'World', + }, + }, + }, + } +}; + +export default () => { + return ( + + + + ); +}; +``` + +### array 类型 schema 的嵌套 + +可以通过 `` 解决自定义嵌套问题 + +#### Array 元素是 string 或 number 时 + +```tsx +import React from 'react'; +import { useFieldSchema, Schema, RecursionField, useField, observer, connect } from '@formily/react'; +import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; + +const useValueSchema = () => { + const schema = useFieldSchema(); + return schema.reduceProperties((buf, s) => { + if (s['x-component'] === 'Value') { + return s; + } + return buf; + }); +}; + +const ArrayList = observer((props) => { + const field = useField(); + const schema = useValueSchema(); + return ( + <> + String Array +
      + {field.value?.map((item, index) => { + // 只有一个元素 + return + })} +
    + + ); +}); + +const Value = connect((props) => { + return
  • value: {props.value}
  • +}); + +const schema = { + type: 'object', + properties: { + strArr: { + type: 'array', + default: [1, 2, 3], + 'x-component': 'ArrayList', + properties: { + value: { + type: 'number', + 'x-component': 'Value', + }, + } + }, + } +}; + +export default () => { + return ( + + + + ); +}; +``` + +#### Array 元素是 Object 时 + +```tsx +import React from 'react'; +import { useFieldSchema, Schema, RecursionField, useField, observer, connect } from '@formily/react'; +import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; + +const ArrayList = observer((props) => { + const field = useField(); + const schema = useFieldSchema(); + // array 类型的 schema 无法 onlyRenderProperties,需要转化为 object 类型 + const objSchema = new Schema({ + type: 'object', + properties: schema.properties, + }); + return ( +
      + {field.value?.map((item, index) => { + // array 元素是 object + return ( + + ) + })} +
    + ); +}); + +const Value = connect((props) => { + return
  • value: {props.value}
  • +}); + +const schema = { + type: 'object', + properties: { + objArr: { + type: 'array', + default: [ + { value: 't1' }, + { value: 't2' }, + { value: 't3' }, + ], + 'x-component': 'ArrayList', + properties: { + value: { + type: 'number', + 'x-component': 'Value', + }, + } + } + } +}; + +export default () => { + return ( + + + + ); +}; +``` + +#### Tree 结构数据 + +```tsx +import { ArrayField } from '@formily/core'; +import { connect, ISchema, observer, RecursionField, useField, useFieldSchema } from '@formily/react'; +import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; +import { Table, TableColumnType } from 'antd'; +import React from 'react'; + +const ArrayTable = observer((props: any) => { + const { rowKey } = props; + const field = useField(); + const schema = useFieldSchema(); + const columnSchemas = schema.reduceProperties((buf, s) => { + if (s['x-component'] === 'ArrayTable.Column') { + buf.push(s); + } + return buf; + }, []); + + const columns = columnSchemas.map((s) => { + return { + render: (value, record) => { + return ; + }, + } as TableColumnType; + }); + + return
    ; +}); + +const Value = connect((props) => { + return
  • value: {props.value}
  • ; +}); + +const schema: ISchema = { + type: 'object', + properties: { + objArr: { + type: 'array', + default: [ + { __path: '0', id: 1, value: 't1' }, + { + __path: '1', + id: 2, + value: 't2', + children: [ + { + __path: '1.children.0', + id: 5, + value: 't5', + parentId: 2, + }, + ], + }, + { + __path: '2', + id: 3, + value: 't3', + children: [ + { + __path: '2.children.0', + id: 4, + value: 't4', + parentId: 3, + children: [ + { + __path: '2.children.0.children.0', + id: 6, + value: 't6', + parentId: 4, + }, + { + __path: '2.children.0.children.1', + id: 7, + value: 't7', + parentId: 4, + }, + ], + }, + ], + }, + ], + 'x-component': 'ArrayTable', + 'x-component-props': { + rowKey: 'id', + }, + properties: { + c1: { + type: 'void', + 'x-component': 'ArrayTable.Column', + properties: { + value: { + type: 'string', + 'x-component': 'Value', + }, + }, + }, + }, + }, + }, +}; + +export default () => { + return ( + + + + ); +}; +``` \ No newline at end of file diff --git a/docs/tr-TR/development/client/ui-schema-designer/index.md b/docs/tr-TR/development/client/ui-schema-designer/index.md new file mode 100644 index 000000000..102eb7c9a --- /dev/null +++ b/docs/tr-TR/development/client/ui-schema-designer/index.md @@ -0,0 +1,3 @@ +# Overview + + \ No newline at end of file diff --git a/docs/tr-TR/development/client/ui-schema-designer/insert-adjacent.md b/docs/tr-TR/development/client/ui-schema-designer/insert-adjacent.md new file mode 100644 index 000000000..f315f0c28 --- /dev/null +++ b/docs/tr-TR/development/client/ui-schema-designer/insert-adjacent.md @@ -0,0 +1,144 @@ +# 邻近位置插入 + +与 DOM 的 [insert adjacent](https://dom.spec.whatwg.org/#insert-adjacent) 概念相似,Schema 也提供了 `dn.insertAdjacent()` 方法用于解决邻近位置的插入问题。 + +## 邻近位置 + +```ts +{ + properties: { + // beforeBegin 在当前节点的前面插入 + node1: { + properties: { + // afterBegin 在当前节点的第一个子节点前面插入 + // ... + // beforeEnd 在当前节点的最后一个子节点后面 + }, + }, + // afterEnd 在当前节点的后面 + }, +} +``` + +## useDesignable() + +获取当前 schema 节点设计器的 API + +```ts +const { + designable, // 是否可以配置 + insertAdjacent, // 在某位置插入,四个位置:beforeBegin、afterBegin、beforeEnd、afterEnd + insertBeforeBegin, // 在当前节点的前面插入 + insertAfterBegin, // 在当前节点的第一个子节点前面插入 + insertBeforeEnd, // 在当前节点的最后一个子节点后面 + insertAfterEnd, // 在当前节点的后面 +} = useDesignable(); + +const schema = { + name: uid(), + 'x-component': 'Hello', +}; + +// 在当前节点的前面插入 +insertBeforeBegin(schema); +// 等同于 +insertAdjacent('beforeBegin', schema); + +// 在当前节点的第一个子节点前面插入 +insertAfterBegin(schema); +// 等同于 +insertAdjacent('afterBegin', schema); + +// 在当前节点的最后一个子节点后面 +insertBeforeEnd(schema); +// 等同于 +insertAdjacent('beforeEnd', schema); + +// 在当前节点的后面 +insertAfterEnd(schema); +// 等同于 +insertAdjacent('afterEnd', schema); +``` + +示例 + +```tsx +import React from 'react'; +import { SchemaComponentProvider, SchemaComponent, useDesignable } from '@nocobase/client'; +import { observer, Schema, useFieldSchema } from '@formily/react'; +import { Button, Space } from 'antd'; +import { uid } from '@formily/shared'; + +const Hello = observer((props) => { + const { insertAdjacent } = useDesignable(); + const fieldSchema = useFieldSchema(); + return ( +
    +

    {fieldSchema.name}

    + + + + + + +
    {props.children}
    +
    + ); +}); + +const Page = observer((props) => { + return
    {props.children}
    ; +}); + +export default () => { + return ( + + + + ); +} +``` diff --git a/docs/tr-TR/development/client/ui-schema-designer/what-is-ui-schema.md b/docs/tr-TR/development/client/ui-schema-designer/what-is-ui-schema.md new file mode 100644 index 000000000..1a4562475 --- /dev/null +++ b/docs/tr-TR/development/client/ui-schema-designer/what-is-ui-schema.md @@ -0,0 +1,329 @@ +# What is UI Schema? + +A protocol for describing front-end components, based on Formily Schema 2.0, JSON Schema-like style. + +```ts +interface ISchema { + type: 'void' | 'string' | 'number' | 'object' | 'array'; + name?: string; + title?: any; + // wrapper component + ['x-decorator']? : string; + // Wrapper component properties + ['x-decorator-props']? : any; + // component + ['x-component']? : string; + // Component properties + ['x-component-props']? : any; + // display state, default is 'visible' + ['x-display']? : 'none' | 'hidden' | 'visible'; + // child node of the component, simply use + ['x-content']? : any; + // children node schema + properties?: Record; + + // The following is used only for field components + + // field linkage + ['x-reactions']? : SchemaReactions; + // Field UI interaction mode, default is 'editable' + ['x-pattern']? : 'editable' | 'disabled' | 'readPretty'; + // Field validation + ['x-validator']? : Validator; + // default data + default: ? :any; + + // Designer related + + // Designer component (toolbar), including: drag and drop to move, insert new nodes, modify parameters, remove, etc. + ['x-designer']? : any; + // Initializer component (toolbar), determines what can be inserted inside the current schema + ['x-initializer']? : any; +} +``` + +## The simplest component + +All native html tags can be converted to schema writing. For example + +```ts +{ + type: 'void', + 'x-component': 'h1', + 'x-content': 'Hello, world! +} +``` + +JSX examples + +```tsx | pure +

    Hello, world!

    +``` + +## children components can be written in properties + +```ts +{ + type: 'void', + 'x-component': 'div', + 'x-component-props': { className: 'form-item' }, + properties: { + title: { + type: 'string', + 'x-component': 'input', + }, + }, +} +``` + +JSX is equivalent to + +```tsx | pure +
    + +
    +``` + +## The clever use of Decorator + +The combination of decorator + component allows you to put two components in a single schema node, reducing the complexity of the schema structure and increasing the reusability of the components. + +For example, in a form scenario, you can combine a FormItem component with any field component, where the FormItem is the Decorator. + +```ts +{ + type: 'void', + ['x-component']: 'div', + properties: { + title: { + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + content: { + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'Input.TextArea', + }, + }, +} +``` + +JSX is equivalent to + +```tsx | pure +
    + + + + + + +
    +``` + +It is also possible to provide a CardItem component that wraps all blocks, so that all blocks are Card wrapped. + +```ts +{ + type: 'void', + ['x-component']: 'div', + properties: { + title: { + type: 'string', + 'x-decorator': 'CardItem', + 'x-component': 'Table', + }, + content: { + type: 'string', + 'x-decorator': 'CardItem', + 'x-component': 'Kanban', + }, + }, +} +``` + +JSX is equivalent to + +```tsx | pure +
    + +
    + + + + + +``` + +## Display state of the component + +- `'x-display': 'visible'`: the component is displayed +- `'x-display': 'hidden'`: component is hidden, data is not hidden +- `'x-display': 'none'`: component is hidden, data is also hidden + +### `'x-display': 'visible'` + +```ts +{ + type: 'void', + 'x-component': 'div', + 'x-component-props': { className: 'form-item' }, + properties: { + title: { + type: 'string', + 'x-component': 'input', + 'x-display': 'visible' + }, + }, +} +``` + +JSX is equivalent to + +```tsx | pure +
    + +
    +``` + +### `'x-display': 'hidden'` + +```ts +{ + type: 'void', + 'x-component': 'div', + 'x-component-props': { className: 'form-item' }, + properties: { + title: { + type: 'string', + 'x-component': 'input', + 'x-display': 'hidden' + }, + }, +} +``` + +JSX is equivalent to + +```tsx | pure +
    + {/* The input component is not output here, the corresponding field model with name=title still exists */} +
    +``` + +### `'x-display': 'none'` + +```ts +{ + type: 'void', + 'x-component': 'div', + 'x-component-props': { className: 'form-item' }, + properties: { + title: { + type: 'string', + 'x-component': 'input', + 'x-display': 'none' + }, + }, +} +``` + +JSX is equivalent to + +```tsx | pure +
    + {/* The input component is not output here, and the corresponding field model with name=title does not exist anymore */} +
    +``` + +## Display modes for components + +For field components, there are three display modes: + +- `'x-pattern': 'editable'` Editable +- `'x-pattern': 'disabled'` Non-editable +- `'x-pattern': 'readPretty'` Friendly reading + +As in the case of the `` component, the editable and disabled modes are `` and the readPretty mode is `
    `. + +### `'x-pattern': 'editable'` + +```ts +const schema = { + name: 'test', + type: 'void', + 'x-component': 'div', + 'x-component-props': { className: 'form-item' }, + properties: { + title: { + type: 'string', + default: 'Hello', + 'x-component': 'SingleText', + 'x-pattern': 'editable' + }, + }, +}; +``` + +JSX is equivalent to + +```tsx | pure +
    + +
    +``` + +### `'x-pattern': 'disabled'` + +```ts +const schema = { + name: 'test', + type: 'void', + 'x-component': 'div', + 'x-component-props': { className: 'form-item' }, + properties: { + title: { + type: 'string', + default: 'Hello', + 'x-component': 'SingleText', + 'x-pattern': 'disabled' + }, + }, +}; +``` + +JSX is equivalent to + +```tsx | pure +
    + +
    +``` + +### `'x-pattern': 'readPretty'` + +```ts +const schema = { + name: 'test', + type: 'void', + 'x-component': 'div', + 'x-component-props': { className: 'form-item' }, + properties: { + title: { + type: 'string', + default: 'Hello', + 'x-component': 'SingleText', + 'x-pattern': 'readPretty', + }, + }, +}; +``` + +JSX is equivalent to + +```tsx | pure +
    +
    Hello
    +
    +``` diff --git a/docs/tr-TR/development/client/ui-schema-designer/x-designer.md b/docs/tr-TR/development/client/ui-schema-designer/x-designer.md new file mode 100644 index 000000000..276fe609c --- /dev/null +++ b/docs/tr-TR/development/client/ui-schema-designer/x-designer.md @@ -0,0 +1,61 @@ +# x-designer 组件 + +## 内置 x-designer 组件 + +- Action.Designer +- Calendar.Designer +- Filter.Action.Designer +- Form.Designer +- FormItem.Designer +- FormV2.Designer +- FormV2.ReadPrettyDesigner +- DetailsDesigner +- G2Plot.Designer +- Kanban.Designer +- Kanban.Card.Designer +- Markdown.Void.Designer +- Menu.Designer +- TableV2.Column.Designer +- TableV2.ActionColumnDesigner +- TableBlockDesigner +- TableSelectorDesigner +- Tabs.Designer + +## 替换 + +```tsx | pure +import React, { useContext } from 'react'; +import { useFieldSchema } from '@formily/react'; +import { + SchemaComponentOptions, + GeneralSchemaDesigner, + SchemaSettings, + useCollection +} from '@nocobase/client'; +import React from 'react'; + +const CustomActionDesigner = () => { + const { name, title } = useCollection(); + const fieldSchema = useFieldSchema(); + return ( + + + + ); +}; + +export default React.memo((props) => { + return ( + {props.children} + ); +}); +``` diff --git a/docs/tr-TR/development/client/ui-schema-designer/x-initializer.md b/docs/tr-TR/development/client/ui-schema-designer/x-initializer.md new file mode 100644 index 000000000..7932e6062 --- /dev/null +++ b/docs/tr-TR/development/client/ui-schema-designer/x-initializer.md @@ -0,0 +1,39 @@ +# x-initializer 组件 + +## 内置 x-initializer 组件 + +- BlockInitializers +- CalendarActionInitializers +- CreateFormBlockInitializers +- CustomFormItemInitializers +- DetailsActionInitializers +- FormActionInitializers +- FormItemInitializers +- KanbanActionInitializers +- ReadPrettyFormActionInitializers +- ReadPrettyFormItemInitializers +- RecordBlockInitializers +- RecordFormBlockInitializers +- SubTableActionInitializers +- TableActionColumnInitializers +- TableActionInitializers +- TableColumnInitializers +- TableSelectorInitializers +- TabPaneInitializers + +## 替换 + +```tsx |pure +import React, { useContext } from 'react'; +import { SchemaInitializerContext } from '@nocobase/client'; + +export default React.memo((props) => { + const items = useContext(SchemaInitializerContext); + const BlockInitializers = {}; + return ( + + {props.children} + + ); +}); +``` diff --git a/docs/tr-TR/development/index.md b/docs/tr-TR/development/index.md new file mode 100644 index 000000000..5e32b1139 --- /dev/null +++ b/docs/tr-TR/development/index.md @@ -0,0 +1,61 @@ +# Introduction + +NocoBase adopts microkernel architecture, functions are extended in the form of plugins. Front and back ends are separated. Various plugin interfaces are provided, and plugins are divided by functional modules with pluggable features. + + + +The pluggable design reduces the coupling between modules and increases the reuse rate. As the plugin library continues to expand, common scenarios require only a combination of plugins to complete the base build. NocoBase's no-code platform, for example, is a combination of various plugins. + + + +## Plugin Manager + +NocoBase provides a powerful plugin manager for managing plugins. The flow of the plugin manager is as follows + + + +No-code Users can manage the activation and deactivation of local plugins through the interface at + + + +Developers can also manage the complete plugin process by way of the CLI: + +```bash +# Create the plugin +yarn pm create hello +# Register the plugin +yarn pm add hello +# Activate the plugin +yarn pm enable hello +# Disable the plugin +yarn pm disable hello +# Remove the plugin +yarn pm remove hello +``` + +For more plugin examples, see [packages/samples](https://github.com/nocobase/nocobase/tree/main/packages/samples). + +## Extensibility + +Whether it is generic functionality or personalization, it is recommended to write it as a plugin. NocoBase is extensible in all aspects. + +- It can be user-intuitive interface-related page modules, block types, action types, field types, etc. +- Filters, validators, access restrictions, etc. for enhancing or restricting the HTTP API +- It can also be enhancements to underlying data tables, migrations, events, command lines, etc. + + +Distribution of modules. + +- Server + - Collections & Fields: mainly used for system table configuration. Business tables are recommended to be configured in "Settings Center - Collection manager". + - Resources & Actions: Mainly used to extend the Action API + - Middleware: Middleware + - Events: Events + - I18n: server-side internationalization + - Commands: Custom command lines + - Migrations: Migration scripts +- Client + - UI Schema Designer: Page Designer + - UI Router: When there is a need for custom pages + - Settings Center: Provides configuration pages for plugins + - I18n: Client side internationalization diff --git a/docs/tr-TR/development/index/app-flow.svg b/docs/tr-TR/development/index/app-flow.svg new file mode 100644 index 000000000..e167cda39 --- /dev/null +++ b/docs/tr-TR/development/index/app-flow.svg @@ -0,0 +1 @@ +
    beforeLoad
    loop: plugin.beforeLoad()
    load/reload
    loop: plugin.load()
    afterLoad
    Reinstall?
    beforeInstall
    install
    afterInstall
    beforeUpgrade
    upgrade
    afterUpgrade
    Restart?
    beforeStart
    start
    afterStart
    beforeStop
    stop
    afterStop
    beforeDestroy
    destroy
    afterDestroy
    \ No newline at end of file diff --git a/docs/tr-TR/development/index/pm-built-in.jpg b/docs/tr-TR/development/index/pm-built-in.jpg new file mode 100644 index 000000000..cca81d62c Binary files /dev/null and b/docs/tr-TR/development/index/pm-built-in.jpg differ diff --git a/docs/tr-TR/development/index/pm-flow.svg b/docs/tr-TR/development/index/pm-flow.svg new file mode 100644 index 000000000..23a738cfc --- /dev/null +++ b/docs/tr-TR/development/index/pm-flow.svg @@ -0,0 +1 @@ +
    Local
    pm.create
    Marketplace
    pm.publish
    NPM registry
    Extracting client files
    pm.add
    plugin.afterAdd()
    app/client plugins
    pm.enable
    plugin.install()
    plugin.afterEnable()
    pm.disable
    plugin.afterDisable()
    pm.remove
    plugin.remove()
    pm.upgrade
    \ No newline at end of file diff --git a/docs/tr-TR/development/index/pm-ui.jpg b/docs/tr-TR/development/index/pm-ui.jpg new file mode 100644 index 000000000..e6a74f84a Binary files /dev/null and b/docs/tr-TR/development/index/pm-ui.jpg differ diff --git a/docs/tr-TR/development/learning-guide.md b/docs/tr-TR/development/learning-guide.md new file mode 100644 index 000000000..cc5744757 --- /dev/null +++ b/docs/tr-TR/development/learning-guide.md @@ -0,0 +1,127 @@ +# 学习路线指南 + +## 1. 从安装运行 NocoBase 开始 + +**相关文档:快速开始** + +主要命令包括: + +下载 + +```bash +yarn create/git clone +yarn install +``` + +安装 + +```bash +yarn nocobase install +``` + +运行 + +```bash +# for development +yarn dev + +# for production +yarn build +yarn start +``` + +## 2. 了解 NocoBase 平台提供的核心功能 + +**相关文档:使用手册** + +主要的三部分包括: + +- UI 设计器:主要包括区块、字段和操作 +- 插件管理器:功能需求扩展 +- 配置中心:已激活插件提供的配置功能 + +## 3. 进一步了解插件管理器的使用 + +**相关文档:插件开发** + +NocoBase 提供了简易的插件管理器界面,但是在界面上只能处理本地插件的 enable、disable 和 remove,完整的操作需要通过 CLI + +```bash +# 创建插件 +yarn pm create hello +# 注册插件 +yarn pm add hello +# 激活插件 +yarn pm enable hello +# 禁用插件 +yarn pm disable hello +# 删除插件 +yarn pm remove hello +``` + +更多插件示例,查看 packages/samples,通过 samples 插件能够了解插件的基本用法,就可以进一步开发插件了。 + +## 4. 开发新插件,了解模块分布 + +**相关文档:扩展指南** + +[编写第一个插件](/development/your-fisrt-plugin) 章节,虽然简单的讲述了插件的主要开发流程,但是为了更快速的介入插件细节,你可能需要进一步了解 NocoBase 框架的模块分布: + +- Server + - Collections & Fields:主要用于系统表配置,业务表建议在「配置中心 - 数据表配置」里配置 + - Resources & Actions:主要用于扩展 Action API + - Middleware:中间件 + - Events:事件 + - I18n:服务端国际化 +- Client + - UI Schema Designer:页面设计器 + - UI Router:有自定义页面需求时 + - Settings Center:为插件提供配置页面 + - I18n:客户端国际化 +- Devtools + - Commands:自定义命令行 + - Migrations:迁移脚本 + +## 5. 查阅各模块主要 API + +**相关文档:API 参考** + +查看各模块的 packages/samples,进一步了解模块主要 API 的用法 + +- Server + - Collections & Fields + - db.collection + - db.import + - Resources & Actions + - app.resourcer.define + - app.resourcer.registerActions + - Middleware + - app.use + - app.acl.use + - app.resourcer.use + - Events + - app.on + - app.db.on + - I18n + - app.i18n + - ctx.i18n +- Client + - UI Schema Designer + - SchemaComponent + - SchemaInitializer + - SchemaSettings + - UI Router + - RouteSwitchProvider + - RouteSwitch + - Settings Center + - SettingsCenterProvider + - I18n + - app.i18n + - useTranslation +- Devtools + - Commands + - app.command + - app.findCommand + - Migrations + - app.db.addMigration + - app.db.addMigrations diff --git a/docs/tr-TR/development/life-cycle.md b/docs/tr-TR/development/life-cycle.md new file mode 100644 index 000000000..f250b26a9 --- /dev/null +++ b/docs/tr-TR/development/life-cycle.md @@ -0,0 +1,41 @@ +# Life cycle + +## Lifecycle of applications + + + +## Lifecycle of plugins + + + +## Lifecycle methods for plugins + +```ts +import { InstallOptions, Plugin } from '@nocobase/server'; + +export class MyPlugin extends Plugin { + afterAdd() { + // After the plugin pm.add is registered. Mainly used to place the app.beforeLoad event. + beforeLoad() { } + beforeLoad() { + // Before all plugins are loaded. Generally used for registering classes and event listeners + } + async load() { + // Load configuration + } + async install(options?: InstallOptions) { + // Logic for installing + } + async afterEnable() { + // After activation + } + async afterDisable() { + // After disable + } + async remove() { + // Logic for removing + } +} + +export default MyPlugin; +``` diff --git a/docs/tr-TR/development/others/build.md b/docs/tr-TR/development/others/build.md new file mode 100644 index 000000000..1294b9a65 --- /dev/null +++ b/docs/tr-TR/development/others/build.md @@ -0,0 +1 @@ +# Building \ No newline at end of file diff --git a/docs/tr-TR/development/others/testing.md b/docs/tr-TR/development/others/testing.md new file mode 100644 index 000000000..1f37805b5 --- /dev/null +++ b/docs/tr-TR/development/others/testing.md @@ -0,0 +1,163 @@ +# 单元测试 + +## 介绍 + +NocoBase 的测试基于 [Jest](https://jestjs.io/) 测试框架。同时,为了方便的编写测试,我们提供了两个工具类,在测试环境模拟正常的数据库和应用的服务端。 + +### MockDatabase + +模拟数据库类继承自 [`Database`](/api/database) 类,大部分内容没有区别,主要在构造函数默认内置了随机表前缀,在每个测试用例初始化数据库时相关数据表都通过前缀名称与其他用例进行隔离,在运行测试用例时互不影响。 + +```ts +import { MockDatabase } from '@nocobase/test'; + +describe('my suite', () => { + let db; + + beforeEach(async () => { + db = new MockDatabase(); + + db.collection({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + } + ] + }); + + await db.sync(); + }); + + test('my case', async () => { + const postRepository = db.getRepository('posts'); + const p1 = await postRepository.create({ + values: { + title: 'hello' + } + }); + + expect(p1.get('title')).toEqual('hello'); + }); +}); +``` + +### MockServer + +模拟服务器也继承自 [Application](/api/server/application) 类,除了内置的数据库实例是通过模拟数据库类生成的以外,还提供了比较方便的生成基于 [superagent](https://www.npmjs.com/package/superagent) 请求代理功能,针对从发送请求到获取响应的写法也集成了 `.resource('posts').create()`,比较简化。 + +```ts +import { mockServer } from '@nocobase/test'; + +describe('my suite', () => { + let app; + let agent; + let db; + + beforeEach(async () => { + app = mockServer(); + agent = app.agent(); + + db.collection({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + } + ] + }); + + await db.sync(); + await app.load(); + }); + + test('my case', async () => { + const { body } = await agent.resource('posts').create({ + values: { + title: 'hello' + } + }); + + expect(body.data.title).toEqual('hello'); + }); +}); +``` + +## 示例 + +我们以之前在 [资源与操作](development/guide/resources-actions) 章节的功能为例,来写一个插件的测试: + +```ts +import { mockServer } from '@nocobase/test'; +import Plugin from '../../src/server'; + +describe('shop actions', () => { + let app; + let agent; + let db; + + beforeEach(async () => { + app = mockServer(); + app.plugin(Plugin); + agent = app.agent(); + db = app.db; + + await app.load(); + await db.sync(); + }); + + afterEach(async () => { + await app.destroy(); + }); + + test('product order case', async () => { + const { body: product } = await agent.resource('products').create({ + values: { + title: 'iPhone 14 Pro', + price: 7999, + enabled: true, + inventory: 1 + } + }); + expect(product.data.price).toEqual(7999); + + const { body: order } = await agent.resource('orders').create({ + values: { + productId: product.data.id + } + }); + expect(order.data.totalPrice).toEqual(7999); + expect(order.data.status).toEqual(0); + + const { body: deliveredOrder } = await agent.resource('orders').deliver({ + filterByTk: order.data.id, + values: { + provider: 'SF', + trackingNumber: '123456789' + } + }); + expect(deliveredOrder.data.status).toBe(2); + expect(deliveredOrder.data.delivery.trackingNumber).toBe('123456789'); + }); +}); +``` + +编写完成后,在命令行中允许测试命令: + +```bash +yarn test packages/samples/shop-actions +``` + +该测试将验证: + +1. 商品可以创建成功; +2. 订单可以创建成功; +3. 订单可以发货成功; + +当然这只是个最基本的例子,从业务上来说并不完善,但作为示例已经可以说明整个测试的流程。 + +## 小结 + +本章涉及的示例代码整合在对应的包 [packages/samples/shop-actions](https://github.com/nocobase/nocobase/tree/main/packages/samples/shop-actions) 中,可以直接在本地运行,查看效果。 diff --git a/docs/tr-TR/development/plugin-ds.md b/docs/tr-TR/development/plugin-ds.md new file mode 100644 index 000000000..798996069 --- /dev/null +++ b/docs/tr-TR/development/plugin-ds.md @@ -0,0 +1,17 @@ +# Plugin directory structure + +An empty plugin can be created quickly with `yarn pm create my-plugin`, with the following directory structure. + +```bash +|- /my-plugin + |- /src + |- /client # client-side of the plugin + |- /server # server-side of the plugin + |- client.d.ts + |- client.js + |- package.json # plugin package information + |- server.d.ts + |- server.js +``` + +The tutorial for `/src/server` refers to the [server](./server) section, and the tutorial for `/src/client` refers to the [client](./client) section. diff --git a/docs/tr-TR/development/server/collections-fields.md b/docs/tr-TR/development/server/collections-fields.md new file mode 100644 index 000000000..e5570aba8 --- /dev/null +++ b/docs/tr-TR/development/server/collections-fields.md @@ -0,0 +1,319 @@ +## Collections and Fields + +## Basic Concepts + +Data modeling is the lowest level foundation of an application. In NocoBase applications we model data through data tables (Collection) and fields (Field), and the modeling is also mapped to database tables for persistence. + +### Collection + +Collection is a collection of all similar data, which corresponds to the concept of database tables in NocoBase. Such as orders, products, users, comments, etc. can form a collection definition. Different collections are distinguished by name and contain fields defined by `fields`, such as + +```ts +db.collection({ + name: 'posts', + fields: [ + { name: 'title', type: 'string' } + { name: 'content', type: 'text' }, + // ... + ] +}); +``` + +The collection is only in memory after the definition, you need to call the [``db.sync()`'' (/api/database#sync) method to synchronize it to the database. + +### Field + +Corresponding to the concept of database table "fields", each data table (Collection) can have a number of Fields, for example. + +```ts +db.collection({ + name: 'users', + fields: [ + { type: 'string', name: 'name' } + { type: 'integer', name: 'age' } + // Other fields + ], +}); +``` + +The field name (`name`) and field type (`type`) are required, and different fields are distinguished by the field name (`name`). All field types and their configurations are described in the [List of built-in field types](/api/database/field#List of built-in field types) section of the API reference. + +## Example + +For developers, we usually build functional collections that are different from normal collections and solidify these collections as part of the plugin and combine them with other data processing processes to form a complete functionality. + +Let's take a simple online store plugin as an example to show how to model and manage the collections of the plugin. Assuming you have already learned about [Develop your first plugin](/development/your-first-plugin), we continue to build on the previous plugin code, except that the name of the plugin is changed from `hello` to `shop-modeling`. + +### Define and create collections in the plugin + +For a store, you first need to create a collection of products, named `products`. Instead of calling [`db.collection()`](/api/database#collection) directly, in the plugin we will use a more convenient method to import multiple files of defined data tables at once. So let's start by creating a file for the product collection definition named ``collections/products.ts`` and fill it with the following content. + +```ts +export default { + name: 'products', + fields: [ + { + type: 'string', + name: 'title' + }, + { + type: 'integer', + name: 'price' + }, + { + type: 'boolean', + name: 'enabled' + }, + { + type: 'integer', + name: 'inventory' + } + ] +}; +``` + +As you can see, the collections structure definition can be used directly in standard JSON format, where `name` and `fields` are required representing the collection's name and the field definitions in the collection. Field definitions similar to Sequelize create system fields such as primary key (`id`), data creation time (`createdAt`) and data update time (`updatedAt`) by default, which can be overridden by a configuration with the same name if there is a special need. + +The data table defined in this file we can introduce and complete the definition in the `load()` cycle of the main plugin class using `db.import()`. This is shown below. + +```ts +import path from 'path'; +import { Plugin } from '@nocobase/server'; + +export default class ShopPlugin extends Plugin { + async load() { + await this.db.import({ + directory: path.resolve(__dirname, 'collections'), + }); + + this.app.acl.allow('products', '*'); + this.app.acl.allow('categories', '*'); + this.app.acl.allow('orders', '*'); + } +} +``` + +In the meantime, for testing purposes, we will temporarily allow all access permissions for the data in these collections, and later we will detail how to manage data permissions in [Permissions Management](/development/guide/acl). + +This way, when the plugin is loaded by the main application, the `products` collection we defined is also loaded into the memory of the database management instance. At the same time, the NocoBase constraint-based resource mapping of the collections automatically generates the corresponding CRUD HTTP API after the application's service is started. + +When the following URLs are requested from the client, the corresponding responses are obtained. + +* `GET /api/products:list`: Get a list of all product data +* `GET /api/products:get?filterByTk=`: Get the product data for the specified ID +* `POST /api/products`: Create a new product data +* `PUT /api/products:update?filterByTk=`: Update a product data +* `DELETE /api/products:destroy?filterByTk=`: Delete a product data + +### Defining associated collections and fields + +In the above example, we only defined a product collection, but in reality a product also needs to be associated to a category, a brand, a supplier, etc. For example, we can define a `categories` collection to store the categories, and then add a `category` field to the product collection to associate it with the category collection. + +Add a new file `collections/categories.ts` and fill in the content. + +```ts +export default { + name: 'categories', + fields: [ + { + type: 'string', + name: 'title' + }, + { + type: 'hasMany', + name: 'products', + } + ] +}; +``` + +We have defined two fields for the `categories` collection, one for the title and another one-to-many field for all the products associated under that category, which will be described later. Since we have already used the `db.import()` method in the plugin's main class to import all the data table definitions under the `collections` directory, the new `categories` collection added here will also be automatically imported into the database management instance. + +Modify the file `collections/products.ts`` to add a `category` field to the `fields`. + +```ts +{ + name: 'products', + fields: [ + // ... + { + type: 'belongsTo', + name: 'category', + target: 'categories', + } + ] +} +``` + +As you can see, the `category` field we added to the `products` collection is a `belongsTo` type field, and its `target` property points to the `categories` collection, thus defining a many-to-one relationship between the `products` collection and the `categories` collection. Combined with the `hasMany` field defined in the `categories` collection, we can achieve a relationship where one product can be associated to multiple categories and multiple products under one category. Usually `belongsTo` and `hasMany` can appear in pairs, defined in two separate collections. + +Once the relationship between the two collections is defined, we can also request the associated data directly through the HTTP API + +* `GET /api/products:list?appends=category`: Get all products data, including the associated categories data +* `GET /api/products:get?filterByTk=&appends=category`: Get the product data for the specified ID, including the associated category data. +* `GET /api/categories//products:list`: Get all the products under the specified category +* `POST /api/categories//products`: Create a new product under the specified category + +Similar to the general ORM framework, NocoBase has four built-in relational field types, for more information you can refer to the section about API field types. + +* [`belongsTo` type](/api/database/field#belongsto) +* [`belongsToMany` type](/api/database/field#belongstomany) +* [`hasMany` type](/api/database/field#hasmany) +* [`hasOne` type](/api/database/field#hasone) + +### Extend an existing collection + +In the above example, we already have a product collection and a category collection, in order to provide the sales process we also need an order collection. We can add a new `orders.ts` file to the `collections` directory and define an `orders` collection as follows + +```ts +export default { + name: 'orders', + fields: [ + { + type: 'uuid', + name: 'id', + primaryKey: true + }, + { + type: 'belongsTo', + name: 'product' + }, + { + type: 'integer', + name: 'quantity' + }, + { + type: 'integer', + name: 'totalPrice' + }, + { + type: 'integer', + name: 'status' + }, + { + type: 'string', + name: 'address' + }, + { + type: 'belongsTo', + name: 'user' + } + ] +} +``` + +For the sake of simplicity, the association between the order collection and the product collection we simply define as a many-to-one relationship, while in the actual business may be used in a complex modeling approach such as many-to-many or snapshot. As you can see, an order in addition to corresponding to a commodity, we also added a relationship definition corresponding to the users, which is a collection managed by the NocoBase built-in plugins (refer to [code for users plugin](https://github.com/nocobase/nocobase/tree/main/packages/) for details plugins/users)). If we want to extend the definition of the "multiple orders owned by a user" relationship for the existing users collection, we can continue to add a new collection file `collections/users.ts` inside the current shop-modeling plugin, which is different from exporting the JSON collection directly. Unlike the direct export of a JSON, the `@nocobase/database` package's `extend()` method is used here to extend the definition of an existing collection: ``ts + +```ts +import { extend } from '@nocobase/database'; + +export extend({ + name: 'users', + fields: [ + { + type: 'hasMany', + name: 'orders' + } + ] +}); +``` + +This way, the existing users table also has an `orders` associated field, and we can retrieve all the order data for a given user via `GET /api/users//orders:list`. + +This method is very useful when extending collections already defined by other plugins, so that other existing plugins do not depend on the new plugin in reverse, but only form one-way dependencies, facilitating a certain degree of decoupling at the extension level. + +### Extend field types + +We use `uuid` type for `id` field when we define order table, which is a built-in field type. Sometimes we may feel that UUID looks too long and waste space, and the query performance is not good, we want to use a more suitable field type, such as a complex numbering logic with date information, or Snowflake algorithm, we need to extend a custom field type. + +Suppose we need to apply the Snowflake ID generation algorithm directly to extend a ``snowflake`` field type, we can create a ``fields/snowflake.ts`` file. + +```ts +// Import the algorithm toolkit +import { Snowflake } from 'nodejs-snowflake'; +// Import field type base class +import { DataTypes, Field, BaseColumnFieldOptions } from '@nocobase/database'; + +export interface SnowflakeFieldOptions extends BaseColumnFieldOptions { + type: 'snowflake'; + epoch: number; + instanceId: number; +} + +export class SnowflakeField extends Field { + get dataType() { + return DataTypes.BIGINT; + } + + constructor(options: SnowflakeFieldOptions, context) { + super(options, context); + + const { + epoch: custom_epoch, + instanceId: instance_id = process.env.INSTANCE_ID ? Number.parseInt(process.env.INSTANCE_ID) : 0, + } = options; + this.generator = new Snowflake({ custom_epoch, instance_id }); + } + + setValue = (instance) => { + const { name } = this.options; + instance.set(name, this.generator.getUniqueID()); + }; + + bind() { + super.bind(); + this.on('beforeCreate', this.setValue); + } + + unbind() { + super.unbind(); + this.off('beforeCreate', this.setValue); + } +} + +export default SnowflakeField; +``` + +Afterwards, register the new field type into the collection in the main plugin file. + +```ts +import SnowflakeField from '. /fields/snowflake'; + +export default class ShopPlugin extends Plugin { + initialize() { + // ... + this.db.registerFieldTypes({ + snowflake: SnowflakeField + }); + // ... + } +} +``` + +This allows us to use the `snowflake` field type in the order table: + +```ts +export default { + name: 'orders', + fields: [ + { + type: 'snowflake' + name: 'id', + primaryKey: true + }, + // ... . other fields + ] +} +``` + +## Summary + +With the above example, we basically understand how to model data in a plugin, including. + +* Defining collections and common fields +* Defining association collections and fields relationships +* Extending fields of an existing collections +* Extending new field types + +We have put the code covered in this chapter into a complete sample package [packages/samples/shop-modeling](https://github.com/nocobase/nocobase/tree/main/packages/samples/shop-modeling), which can be run directly locally to see the results. + diff --git a/docs/tr-TR/development/server/collections/association-fields.md b/docs/tr-TR/development/server/collections/association-fields.md new file mode 100644 index 000000000..c6a873e07 --- /dev/null +++ b/docs/tr-TR/development/server/collections/association-fields.md @@ -0,0 +1,153 @@ +# Association Fields + +In a relational database, the standard way to build a table relationship is to add a foreign key field followed by a foreign key constraint. For example, Knex builds a table with the following example. + +```ts +knex.schema.table('posts', function (table) { + table.integer('userId').unsigned(); + table.foreign('userId').references('users.id'); +}); +``` + +This procedure creates a userId field in the posts table and sets the foreign key constraint posts.userId to refer to users.id. In NocoBase's Collection, such a relational constraint is created by configuring the relational field, e.g. + +```ts +{ + name: 'posts', + fields: [ + { + type: 'belongsTo', + name: 'user', + target: 'users', + foreignKey: 'userId', + }, + ], +} +``` + +## Relationship parameters + +### BelongsTo + +```ts +interface BelongsTo { + type: 'belongsTo'; + name: string; + // defaults to name's plural + target?: string; + // The default value is the primary key of the target model, usually 'id' + targetKey?: any; + // defaults to target + 'Id' + foreignKey?: any; +} + +// The authors table's primary key id is concatenated with the books table's foreign key authorId +{ + name: 'books', + fields: [ + { + type: 'belongsTo', + name: 'author', + target: 'authors', + targetKey: 'id', // authors table's primary key + foreignKey: 'authorId', // foreign key in books table + } + ], +} +``` + +### HasOne + +```ts +interface HasOne { + type: 'hasOne'; + name: string; + // defaults to name's plural + target?: string; + // The default value is the primary key of the source model, usually 'id' + sourceKey?: string; + // default value is the singular form of source collection name + 'Id' + foreignKey?: string; +foreignKey?} + +// The users table's primary key id is concatenated with the profiles' foreign key userId +{ + name: 'users', + fields: [ + { + type: 'hasOne', + name: 'profile', + target: 'profiles', + sourceKey: 'id', // users table's primary key + foreignKey: 'userId', // foreign key in profiles table + } + ], +} +``` + +### HasMany + +```ts +interface HasMany { + type: 'hasMany'; + name: string; + // defaults to name + target?: string; + // The default value is the primary key of the source model, usually 'id' + sourceKey?: string; + // the default value is the singular form of the source collection name + 'Id' + foreignKey?: string; +} + +// The posts table's primary key id is concatenated with the comments table's postId +{ + name: 'posts', + fields: [ + { + type: 'hasMany', + name: 'comments', + target: 'comments', + sourceKey: 'id', // posts table's primary key + foreignKey: 'postId', // foreign key in the comments table + } + ], +} +``` + +### BelongsToMany + +```ts +interface BelongsToMany { + type: 'belongsToMany'; + name: string; + // default value is name + target?: string; + // defaults to the source collection name and target in the natural order of the first letter of the string + through?: string; + // defaults to the singular form of source collection name + 'Id' + foreignKey?: string; + // The default value is the primary key of the source model, usually id + sourceKey?: string; + // the default value is the singular form of target + 'Id' + otherKey?: string; + // the default value is the primary key of the target model, usually id + targetKey?: string; +} + +// tags table's primary key, posts table's primary key and posts_tags two foreign keys are linked +{ + name: 'posts', + fields: [ + { + type: 'believesToMany', + name: 'tags', + target: 'tags', + through: 'posts_tags', // intermediate table + foreignKey: 'tagId', // foreign key 1, in posts_tags table + otherKey: 'postId', // foreignKey2, in posts_tags table + targetKey: 'id', // tags table's primary key + sourceKey: 'id', // posts table's primary key + } + ], +} +``` diff --git a/docs/tr-TR/development/server/collections/cm.svg b/docs/tr-TR/development/server/collections/cm.svg new file mode 100644 index 000000000..9be5cbf5f --- /dev/null +++ b/docs/tr-TR/development/server/collections/cm.svg @@ -0,0 +1 @@ +
    Collection Manager Plugin

    Provide rest api to manage collections
    REST API
    Plugin A
    db.collection()
    Collections
    UI: Collections & Fields
    UI: Graphical interface
    Plugin B
    db.collection()
    Third party
    \ No newline at end of file diff --git a/docs/tr-TR/development/server/collections/collection-field.svg b/docs/tr-TR/development/server/collections/collection-field.svg new file mode 100644 index 000000000..77c6d5f6f --- /dev/null +++ b/docs/tr-TR/development/server/collections/collection-field.svg @@ -0,0 +1 @@ +
    Field Type
    Field Component
    Field Interface
    Collection Field
    \ No newline at end of file diff --git a/docs/tr-TR/development/server/collections/collection-template.md b/docs/tr-TR/development/server/collections/collection-template.md new file mode 100644 index 000000000..eeeb27eca --- /dev/null +++ b/docs/tr-TR/development/server/collections/collection-template.md @@ -0,0 +1,82 @@ +# Collection templates + + +📢 Collection templates are scheduled to be available in Q4 2022. + + +In real business scenarios, different collections may have their own initialization rules and business logic, and NocoBase addresses such issues by providing collection templates. + +## General collections + +```ts +db.collection({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + } + ], +}); +``` + +## Tree structure collections + +```ts +db.collection({ + name: 'categories', + tree: 'adjacency-list', + fields: [ + { + type: 'string', + name: 'name', + }, + { + type: 'string', + name: 'description', + }, + { + type: 'belongsTo', + name: 'parent', + target: 'categories', + foreignKey: 'parentId', + }, + { + type: 'hasMany', + name: 'children', + target: 'categories', + foreignKey: 'parentId', + }, + ], +}); +``` + +## Parent-child inheritance collections + +```ts +db.collection({ + name: 'a', + fields: [ + + ], +}); + +db.collection({ + name: 'b', + inherits: 'a', + fields: [ + + ], +}); +``` + +## More templates + +As in the case of calendar collections, each initialized collection needs to be initialized with special cron and exclude fields, and the definition of such fields is done by the template + +```ts +db.collection({ + name: 'events', + template: 'calendar', +}); +``` diff --git a/docs/tr-TR/development/server/collections/configure.md b/docs/tr-TR/development/server/collections/configure.md new file mode 100644 index 000000000..a3346e519 --- /dev/null +++ b/docs/tr-TR/development/server/collections/configure.md @@ -0,0 +1,62 @@ +# How to configure collections? + +NocoBase has three ways to configure collections. + + + +## Configuring collections through the interface + +Business data is generally recommended to be configured using the interface, and the NocoBase platform provides two interfaces to configure collections. + +### Regular table interface + + + +### Graphical configuration interface + + + +## Defined in the plugin code + +Generally used to configure plugin functions or system configuration tables where users can read and write data, but cannot modify the data structure. + +```ts +export class MyPlugin extends Plugin { + load() { + this.db.collection(); + this.db.import(); + } +} +``` + +Related API Reference + +- [db.collection()](/api/database#collection) +- [db.import()](/api/database#import) + +The collection configured in the plugin is automatically synchronized with the database when the plugin is activated, giving birth to the corresponding data tables and fields. + +## Managing data tables via REST API + +Third parties can also manage data tables via the HTTP interface (permissions required) + +### Collections + +```bash +GET /api/collections +POST /api/collections +GET /api/collections/ +PUT /api/collections/ +DELETE /api/collections/ +``` + +### Collection fields + +```bash +GET /api/collections//fields +POST /api/collections//fields +GET /api/collections//fields/ +PUT /api/collections//fields/ +DELETE /api/collections//fields/ +``` + diff --git a/docs/tr-TR/development/server/collections/field-extension.md b/docs/tr-TR/development/server/collections/field-extension.md new file mode 100644 index 000000000..3c95ee52e --- /dev/null +++ b/docs/tr-TR/development/server/collections/field-extension.md @@ -0,0 +1,39 @@ +# How to extend fields + +The composition of a Collection Field in NocoBase consists of + + + +## Extend Field Type + +For example, to extend the password type field ``type: 'password'` + +```ts +export class MyPlugin extends Plugin { + beforeLoad() { + this.db.registerFieldTypes({ + password: PasswordField + }); + } +} + +export class PasswordField extends Field { + get dataType() { + return DataTypes.STRING; + } +} +``` + +- [More implementations of the built-in field types can be found here](https://github.com/nocobase/nocobase/tree/main/packages/core/database/src/fields) +- Also see the full samples plugin [packages/samples/shop-modeling](https://github.com/nocobase/nocobase/tree/main/packages/samples/shop-modeling) + +## Extend Field Component + +Related extension documentation can be found at + +- [Extending Schema Components](/development/client/ui-schema-designer/extending-schema-components) +- [Schema component library](/development/client/ui-schema-designer/component-library) + +## Extend Field Interface + +- [Built-in field interfaces view here](https://github.com/nocobase/nocobase/tree/main/packages/core/client/src/collection-manager/interfaces) diff --git a/docs/tr-TR/development/server/collections/graph.jpg b/docs/tr-TR/development/server/collections/graph.jpg new file mode 100644 index 000000000..c4653b6ae Binary files /dev/null and b/docs/tr-TR/development/server/collections/graph.jpg differ diff --git a/docs/tr-TR/development/server/collections/index.md b/docs/tr-TR/development/server/collections/index.md new file mode 100644 index 000000000..27e7d7dd9 --- /dev/null +++ b/docs/tr-TR/development/server/collections/index.md @@ -0,0 +1,194 @@ +# Core concepts + +## Collection + +Collection is a collection of all kinds of data, such as orders, products, users, comments, etc. Different collections are distinguished by name, e.g. + +```ts +// Orders +{ + name: 'orders', +} +// Products +{ + name: 'products', +} +// Users +{ + name: 'users', +} +// Comments +{ + name: 'comments', +} +``` + +## Collection Field + +Each Collection has a number of Fields. + +```ts +// Collection configuration +{ + name: 'users', + fields: [ + { type: 'string', name: 'name' }, + { type: 'integer', name: 'age' }, + // Other fields + ], +} +// sample data +[ + { + name: 'Jason', + age: 20, + }, + { { + name: 'Li Si', + age: 18, + } +]; +``` + +The composition of a Collection Field in NocoBase consists of + + + +### Field Type + +Different fields are distinguished by name, and type indicates the data type of the field, which is divided into Attribute Type and Association Type, e.g. + +**Attribute - Attribute Type** + +- string +- text +- date +- boolean +- time +- float +- json +- location +- password +- virtual +- ... + +**Relationship - Association Type** + +- hasOne +- hasMany +- belongsTo +- belongsToMany +- ... + +### Field Component + +The field has a data type, the IO of the field value is fine, but it is not enough, if you need to display the field on the interface, you need another dimension of configuration -- `uiSchema`, e.g. + +```tsx | pure +// Email field, displayed with Input component, using email validation rules +{ + type: 'string', + name: 'email', + uiSchema: { + 'x-component': 'Input', + 'x-component-props': { size: 'large' }, + 'x-validator': 'email', + 'x-pattern': 'editable', // editable state, and readonly state, read-pretty state + }, +} + +// Example data +{ + email: 'admin@nocobase.com', +} + +// Component example + +``` + +The uiSchema is used to configure the components of the field to be displayed on the interface, each field component will correspond to a value and includes several maintained configurations: + +- The component of the field +- The parameters of the component +- The field's validation rules +- The mode of the field (editable, readonly, read-pretty) +- The default value of the field +- Other + +[see the UI Schema chapter for more information](/development/client/ui-schema-designer/what-is-ui-schema). + +The built-in field components of NocoBase are + +- Input +- InputNumber +- Select +- Radio +- Checkbox +- ... + +### Field Interface + +With Field Type and Field Component you can freely combine several fields, we call this combined template Field Interface, e.g. + +```ts +// email field, string + input, email validation rules +{ + type: 'string', + name: 'email', + uiSchema: { + 'x-component': 'Input', + 'x-component-props': {}, + 'x-validator': 'email', + }, +} + +// phone field, string + input, phone validation rules +{ + type: 'string', + name: 'phone', + uiSchema: { + 'x-component': 'Input', + 'x-component-props': {}, + 'x-validator': 'phone', + }, +} +``` + +The above email and phone require a full uiSchema to be configured each time which is very tedious. To simplify the configuration, another concept Field interface is introduced, which can template some parameters, e.g. + +```ts +// Template for the email field +interface email { + type: 'string'; + uiSchema: { + 'x-component': 'Input', + 'x-component-props': {}, + 'x-validator': 'email', + }; +} + +// Template for the phone field +interface phone { + type: 'string'; + uiSchema: { + 'x-component': 'Input', + 'x-component-props': {}, + 'x-validator': 'phone', + }; +} + +// Simplified field configuration +// email +{ + interface: 'email', + name: 'email', +} + +// phone +{ + interface: 'phone', + name: 'phone', +} +``` + +[More Field Interface here](https://github.com/nocobase/nocobase/tree/main/packages/core/client/src/collection-manager/interfaces) diff --git a/docs/tr-TR/development/server/collections/options.md b/docs/tr-TR/development/server/collections/options.md new file mode 100644 index 000000000..319edf654 --- /dev/null +++ b/docs/tr-TR/development/server/collections/options.md @@ -0,0 +1,137 @@ +# Collection protocol + +Collection is the backbone of NocoBase, a protocol for describing data structures (collections and fields), very close to the concept of a relational database, but not limited to relational databases, but can also be a data source for NoSQL databases, HTTP APIs, etc. + + + +At this stage, the Collection protocol is based on the relational database interface (db.collections), and data sources such as NoSQL databases and HTTP APIs will be implemented gradually in the future. + +Collection protocol mainly includes two parts: CollectionOptions and FieldOptions. Because Field is extensible, the parameters of FieldOptions are very flexible. + +## CollectionOptions + +```ts +interface CollectionOptions { + name: string; + title?: string; + // Tree structure table, TreeRepository + tree?: 'adjacency-list' | 'closure-table' | 'materialized-path' | 'nested-set'; + // parent-child inheritance + inherits?: string | string[]; + fields?: FieldOptions[]; + timestamps?: boolean; + paranoid?: boolean; + sortable?: CollectionSortable; + model?: string; + repository?: string; + [key: string]: any; +} + +type CollectionSortable = string | boolean | { name?: string; scopeKey?: string }; +``` + +## FieldOptions + +Generic field parameters + +```ts +interface FieldOptions { + name: string; + type: string; + hidden?: boolean; + index?: boolean; + interface?: string; + uiSchema?: ISchema; +``` + +[Introduction to UI Schema here](/development/client/ui-schema-designer/what-is-ui-schema) + +### Field Type + +Field Type includes Attribute Type and Association Type. + +**Attribute Type** + +- 'boolean' +- 'integer' +- 'bigInt' +- 'double' +- 'real' +- 'decimal' +- 'string' +- 'text' +- 'password' +- 'date' +- 'time' +- 'array' +- 'json' +- 'jsonb' +- 'uuid' +- 'uid' +- 'formula' +- 'radio' +- 'sort' +- 'virtual' + +**Association Type** + +- 'belongsTo' +- 'hasOne' +- 'hasMany' +- 'belongsToMany' + +### Field Interface + +**Basic** + +- input +- textarea +- phone +- email +- integer +- number +- percent +- password +- icon + +**Choices** + +- checkbox +- select +- multipleSelect +- radioGroup +- checkboxGroup +- chinaRegion + +**Media** + +- attachment +- markdown +- richText + +**Date & Time** + +- datetime +- time + +**Relation** + +- linkTo - `type: 'believesToMany'` +- oho - `type: 'hasOne'` +- obo - `type: 'believesTo'` +- o2m - `type: 'hasMany'` +- m2o - `type: 'believesTo'` +- m2m - `type: 'believesToMany'` + +**Advanced** + +- formula +- sequence + +**System info** + +- id +- createdAt +- createdBy +- updatedAt +- updatedBy diff --git a/docs/tr-TR/development/server/collections/schema.svg b/docs/tr-TR/development/server/collections/schema.svg new file mode 100644 index 000000000..230a9bbca --- /dev/null +++ b/docs/tr-TR/development/server/collections/schema.svg @@ -0,0 +1 @@ +
    Collection
    Field Type
    Field Component
    UI Schema
    Field Interface
    Collection Field
    Relational Database
    PostgreSQL
    NoSQL
    API
    SQLite
    MySQL
    Others
    Resource
    Client
    \ No newline at end of file diff --git a/docs/tr-TR/development/server/collections/table.jpg b/docs/tr-TR/development/server/collections/table.jpg new file mode 100644 index 000000000..f1123273e Binary files /dev/null and b/docs/tr-TR/development/server/collections/table.jpg differ diff --git a/docs/tr-TR/development/server/commands.md b/docs/tr-TR/development/server/commands.md new file mode 100644 index 000000000..069e1f354 --- /dev/null +++ b/docs/tr-TR/development/server/commands.md @@ -0,0 +1,100 @@ +# Commands + +NocoBase Server Application is a powerful and extensible CLI tool in addition to being used as a WEB server. + +Create a new `app.js` file with the following code. + +```ts +const Application = require('@nocobase/server'); + +// omit the specific configuration here +const app = new Application({/*... */}); + +app.runAsCLI(); +``` + +app.js run as ``runAsCLI()`` is a CLI, it will work like this in the command line tool. + +```bash +node app.js install # install +node app.js start # start +``` + +To better develop, build and deploy NocoBase applications, NocoBase has many built-in commands, see the [NocoBase CLI](/api/cli) section for details. + +## How to customize Command? + +NocoBase CLI is designed to be very similar to [Laravel Artisan](https://laravel.com/docs/9.x/artisan), both are extensible. NocoBase CLI is based on [commander](https://www.npmjs.com/ package/commander) implementation, which extends Command like this + +```ts +export class MyPlugin extends Plugin { + load() { + this.app + .command('echo') + .option('--v, --version'); + .action(async ([options]) => { + console.log('Hello World!'); + if (options.version) { + console.log('Current version:', app.getVersion()); + } + }); + } +} +``` + +This method defines the following command. + +```bash +yarn nocobase echo +# Hello World! +yarn nocobase echo -v +# Hello World! +# Current version: 0.8.0-alpha.1 +``` + +More API details can be found in the [Application.command()](/api/server/application#command) section. + +## Example + +### Defining a command for exporting collections + +If we want to export the data in the application's collections to a JSON file, we can define a subcommand as follows. + +```ts +import path from 'path'; +import * as fs from 'fs/promises'; + +class MyPlugin extends Plugin { + load() { + this.app + .command('export') + .option('-o, --output-dir') + .action(async (options, . .collections) => { + const { outputDir = path.join(process.env.PWD, 'storage') } = options; + await collections.reduce((promise, collection) => promise.then(async () => { + if (!this.db.hasCollection(collection)) { + console.warn('No such collection:', collection); + return; + } + + const repo = this.db.getRepository(collection); + const data = repo.find(); + await fs.writeFile(path.join(outputDir, `${collection}.json`), JSON.stringify(data), { mode: 0o644 }); + }), Promise.resolve()); + }); + } +} +``` + +After registering and activating the plugin call from the command line. + +```bash +mkdir -p . /storage/backups +yarn nocobase export -o . /storage/backups users +``` + +After execution, it will generate `. /storage/backups/users.json` file containing the data from the collections. + +## Summary + +The sample code covered in this chapter is integrated in the [packages/samples/command](https://github.com/nocobase/nocobase/tree/main/packages/samples/command) package and can be run directly locally to see the results. diff --git a/docs/tr-TR/development/server/events.md b/docs/tr-TR/development/server/events.md new file mode 100644 index 000000000..15e2a6a96 --- /dev/null +++ b/docs/tr-TR/development/server/events.md @@ -0,0 +1,168 @@ +# Events + +NocoBase provides a very large number of event listeners in the lifecycle of applications, plugins, and database, and these methods will only be executed when an event is triggered. + +## How to add event listeners? + +The registration of events is usually placed in afterAdd or beforeLoad + +```ts +export class MyPlugin extends Plugin { + // After the plugin is added, afterAdd() is executed with or without activation + afterAdd() { + this.app.on(); + this.db.on(); + } + + // beforeLoad() will only be executed after the plugin is activated + beforeLoad() { + this.app.on(); + this.db.on(); + } +} +``` + +### `db.on` + +Database related events are related to Collection configuration, CRUD of Repository, including: + +- 'beforeSync' / 'afterSync' +- 'beforeValidate' / 'afterValidate' +- 'beforeCreate' / 'afterCreate' +- 'beforeUpdate' / 'afterUpdate' +- 'beforeSave' / 'afterSave' +- 'beforeDestroy' / 'afterDestroy' +- 'afterCreateWithAssociations' +- 'afterUpdateWithAssociations' +- 'afterSaveWithAssociations' +- 'beforeDefineCollection' +- 'afterDefineCollection' +- 'beforeRemoveCollection' / 'afterRemoveCollection' + +See [Database API](/api/database) for more details. + +### `app.on()` + +The app's events are related to the application's lifecycle, and the relevant events are: + +- 'beforeLoad' / 'afterLoad' +- 'beforeInstall' / 'afterInstall' +- 'beforeUpgrade' / 'afterUpgrade' +- 'beforeStart' / 'afterStart' +- 'beforeStop' / 'afterStop' +- 'beforeDestroy' / 'afterDestroy' + +Refer to [Application API](/api/server/application#Events) for more details. + +## Example + +Let's continue with a simple online store as an example, the related collections modeling can be reviewed in the [Collections and Fields](/development/) section for examples. + +### Deducting product inventory after creating an order + +Usually we have different collections for products and orders. The problem of overselling can be solved by subtracting the inventory of the item after the customer has placed the order. At this point we can define the corresponding event for the action of Creating Order and solve the inventory modification problem at this time together with: + +```ts +class ShopPlugin extends Plugin { + beforeLoad() { + this.db.on('orders.afterCreate', async (order, options) => { + const product = await order.getProduct({ + transaction: options.transaction + }); + + await product.update({ + inventory: product.inventory - order.quantity + }, { + transaction: options.transaction + }); + }); + } +} +``` + +Since the default Sequelize event carries information about the transaction, we can use transaction directly to ensure that both data actions are performed in the same transaction. + +Similarly, you can change the order status to shipped after creating the shipping record: ```ts + +```ts +class ShopPlugin extends Plugin { + load() { + this.db.on('deliveries.afterCreate', async (delivery, options) => { + const orderRepo = this.db.getRepository('orders'); + await orderRepo.update({ + filterByTk: delivery.orderId, + value: { + status: 2 + } + transaction: options.transaction + }); + }); + } +} +``` + +### Timed tasks that exist alongside the application + +Without considering complex cases such as using workflow plugins, we can also implement a simple timed task mechanism via application-level events, and it can be bound to the application's process and stop when it exits. For example, if we want to scan all orders at regular intervals and automatically sign them after the sign-off time. + +```ts +class ShopPlugin extends Plugin { + timer = null; + orderReceiveExpires = 86400 * 7; + + checkOrder = async () => { + const expiredDate = new Date(Date.now() - this.orderReceiveExpires); + const deliveryRepo = this.db.getRepository('deliveries'); + const expiredDeliveries = await deliveryRepo.find({ + fields: ['id', 'orderId'], + filter: { + status: 0, + createdAt: { + $lt: expiredDate + } + } + }); + await deliveryRepo.update({ + filter: { + id: expiredDeliveries.map(item => item.get('id')), + }, + values: { + status: 1 + } + }); + const orderRepo = this.db.getRepository('orders'); + const [updated] = await orderRepo.update({ + filter: { + status: 2, + id: expiredDeliveries.map(item => item.get('orderId')) + }, + values: { + status: 3 + } + }); + + console.log('%d orders expired', updated); + }; + + load() { + this.app.on('beforeStart', () => { + // execute every minute + this.timer = setInterval(this.checkOrder, 1000 * 60); + }); + + this.app.on('beforeStop', () => { + clearInterval(this.timer); + this.timer = null; + }); + } +} +``` + +## Summary + +The above example gives us a basic understanding of what events do and the ways they can be used to extend. + +* Database related events +* Application related events + +The sample code covered in this chapter is integrated in the corresponding package [packages/samples/shop-events](https://github.com/nocobase/nocobase/tree/main/packages/samples/shop-events), which can be run directly in run locally to see the results. diff --git a/docs/tr-TR/development/server/i18n.md b/docs/tr-TR/development/server/i18n.md new file mode 100644 index 000000000..e0b68cc59 --- /dev/null +++ b/docs/tr-TR/development/server/i18n.md @@ -0,0 +1,134 @@ +# Internationalization + +Internationalization in NocoBase is implemented based on [i18next](https://npmjs.com/package/i18next). + +## How to register a multilingual package? + +```ts +export class MyPlugin extends Plugin { + load() { + this.app.i18n.addResources('zh-CN', 'test', { + Hello: '你好', + World: '世界', + }); + this.app.i18n.addResources('en-US', 'test', { + Hello: 'Hello', + World: 'World', + }); + } +} +``` + +## Two i18n instances + +### app.i18n + +Global i18n instance, typically used in the CLI. + +```ts +app.i18n.t('World') // "世界" or "World" +``` + +### ctx.i18n + +CloneInstance of global i18n with a completely independent context for each request, typically used to respond to multilingual messages based on the client language. + +```ts +app.use(async (ctx, next) => { + ctx.body = `${ctx.i18n.t('Hello')} ${ctx.i18n.t('World')}`; + await next(); +}); +``` + +The client request parameters can be placed in the query string + +```bash +GET /?locale=en-US HTTP/1.1 +Host: localhost:13000 +``` + +or in the request headers + +```bash +GET / HTTP/1.1 +Host: localhost:13000 +X-Locale: en-US +``` + +## Suggested configuration + +With English text as the key and translation as the value, this has the advantage that even if multiple languages are missing, it will be displayed in English and will not cause reading barriers, e.g. + +```ts +i18n.addResources('zh-CN', 'your-namespace', { + 'Show dialog': '显示对话框', + 'Hide dialog': '隐藏对话框' +}); +``` + +To make it easier to manage multilingual files, it is recommended to create a `locale` folder in the plugin and place all the corresponding language files in it: + +```bash +|- /my-plugin + |- /src + |- /server + |- locale # Multi-language folder + |- en-cn.ts + |- en-US.ts +``` + +## Example + +### Server-side error alert + +For example, when a user places an order for a product in the store, if the product is not in stock, or not on the shelf, then the order interface should return the appropriate error when it is called. + +```ts +const namespace = 'shop'; + +export default class ShopPlugin extends Plugin { + async load() { + this.app.i18n.addResources('zh-CN', namespace, { + 'No such product': '商品不存在', + 'Product not on sale': '商品已下架', + 'Out of stock': '库存不足', + }); + + this.app.resource({ + name: 'orders', + actions: { + async create(ctx, next) { + const productRepo = ctx.db.getRepository('products'); + const product = await productRepo.findOne({ + filterByTk: ctx.action.params.values.productId + productId }); + + if (!product) { + return ctx.throw(404, ctx.t('No such product')); + } + + if (!product.enabled) { + return ctx.throw(400, ctx.t('Product not on sale')); + } + + if (!product.inventory) { + return ctx.throw(400, ctx.t('Out of stock')); + } + + const orderRepo = ctx.db.getRepository('orders'); + ctx.body = await orderRepo.create({ + values: { + productId: product.id, + quantity: 1, + totalPrice: product.price, + userId: ctx.state.currentUser.id + } + }); + + next(); + } + } + }); + } +} +``` diff --git a/docs/tr-TR/development/server/index.md b/docs/tr-TR/development/server/index.md new file mode 100644 index 000000000..b220c425d --- /dev/null +++ b/docs/tr-TR/development/server/index.md @@ -0,0 +1,72 @@ +# Overview + +The server-side for an initialized, empty plugin has the following directory structure. + +```bash +|- /my-plugin + |- /src + |- /server # Server-side code of the plugin + |- plugin.ts # Classes of the plugin + |- index.ts # server-side entry + |- server.d.ts + |- server.js +``` + +`plugin.ts` provides calls to various methods of the plugin lifecycle + +```ts +import { InstallOptions, Plugin } from '@nocobase/server'; + +export class MyPlugin extends Plugin { + afterAdd() { + // After the plugin pm.add is registered. This is mainly used to place a listener for the app beforeLoad event + this.app.on('beforeLoad'); + } + beforeLoad() { + // Custom classes or methods + this.db.registerFieldTypes() + this.db.registerModels() + this.db.registerRepositories() + this.db.registerOperators() + // Event Listening + this.app.on(); + this.db.on(); + } + async load() { + // Define collection + this.db.collection(); + // Import collection + this.db.import(); + this.db.addMigrations(); + + // Define resource + this.resourcer.define(); + // resource action + this.resourcer.registerActions(); + + // Register middleware + this.resourcer.use(); + this.acl.use(); + this.app.use(); + + // Customize the multilingual package + this.app.i18n.addResources(); + // Customize command line + this.app.command(); + } + async install(options?: InstallOptions) { + // Logic for installing + } + async afterEnable() { + // After activation + } + async afterDisable() { + // After disable + } + async remove() { + // Logic for removing + } +} + +export default MyPlugin; +``` diff --git a/docs/tr-TR/development/server/middleware.md b/docs/tr-TR/development/server/middleware.md new file mode 100644 index 000000000..80f156b53 --- /dev/null +++ b/docs/tr-TR/development/server/middleware.md @@ -0,0 +1,160 @@ +# Middleware + +## How to register middleware? + +The registration method for middleware is usually written in the load method + +```ts +export class MyPlugin extends Plugin { + load() { + this.app.acl.use(); + this.app.resourcer.use(); + this.app.use(); + } +} +``` + +Notes. + +1. `app.acl.use()` Add a resource-permission-level middleware to be executed before permission determination +2. `app.resourcer.use()` Adds a resource-level middleware that is executed only when a defined resource is requested +3. `app.use()` Add an application-level middleware to be executed on every request + +## Onion Circle Model + +```ts +app.use(async (ctx, next) => { + ctx.body = ctx.body || []; + ctx.body.push(1); + await next(); + ctx.body.push(2); +}); + +app.use(async (ctx, next) => { + ctx.body = ctx.body || []; + ctx.body.push(3); + await next(); + ctx.body.push(4); +}); +``` + +Visit http://localhost:13000/api/hello to see that the browser responds with the following data + +```js +{"data": [1,3,4,2]} +``` + +## Built-in middlewares and execution order + +1. `cors` +2. `bodyParser` +3. `i18n` +4. `dataWrapping` +5. `db2resource` 6. +6. `restApi` 1. + 1. `parseToken` 2. + 2. `checkRole` + 3. `acl` 1. + 1. `acl.use()` Additional middleware added + 4. `resourcer.use()` Additional middleware added + 5. `action handler` +7. `app.use()` Additional middleware added + +You can also use `before` or `after` to insert the middleware into the location of one of the preceding `tag`, e.g. + +```ts +app.use(m1, { tag: 'restApi' }); +app.resourcer.use(m2, { tag: 'parseToken' }); +app.resourcer.use(m3, { tag: 'checkRole' }); +// m4 will come before m1 +app.use(m4, { before: 'restApi' }); +// m5 will be inserted between m2 and m3 +app.resourcer.use(m5, { after: 'parseToken', before: 'checkRole' }); +``` + +If no location is specifically specified, the order of execution of the added middlewares is + +1. middlewares added by `acl.use` will be executed first +2. then the ones added by `resourcer.use`, including the middleware handler and action handler +3. and finally the ones added by `app.use` + +```ts +app.use(async (ctx, next) => { + ctx.body = ctx.body || []; + ctx.body.push(1); + await next(); + ctx.body.push(2); +}); + +app.resourcer.use(async (ctx, next) => { + ctx.body = ctx.body || []; + ctx.body.push(3); + await next(); + ctx.body.push(4); +}); + +app.acl.use(async (ctx, next) => { + ctx.body = ctx.body || []; + ctx.body.push(5); + await next(); + ctx.body.push(6); +}); + +app.resourcer.define({ + name: 'test', + actions: { + async list(ctx, next) { + ctx.body = ctx.body || []; + ctx.body.push(7); + await next(); + ctx.body.push(8); + }, + }, +}); +``` + +Visit http://localhost:13000/api/hello to see that the browser responds with the data + +```js +{"data": [1,2]} +``` + +Visiting http://localhost:13000/api/test:list to see, the browser responds with the following data + +```js +{"data": [5,3,7,1,2,8,4,6]} +``` + +### Resource undefined, middlewares added by resourcer.use() will not be executed + +```ts +app.use(async (ctx, next) => { + ctx.body = ctx.body || []; + ctx.body.push(1); + await next(); + ctx.body.push(2); +}); + +app.resourcer.use(async (ctx, next) => { + ctx.body = ctx.body || []; + ctx.body.push(3); + await next(); + ctx.body.push(4); +}); +``` + +Visit http://localhost:13000/api/hello to see that the browser responds with the following data + +```js +{"data": [1,2]} +``` + +In the above example, the hello resource is not defined and will not enter the resourcer, so the middleware in the resourcer will not be executed + +## Middleware Usage + +TODO + +## Example + +- [samples/ratelimit](https://github.com/nocobase/nocobase/blob/main/packages/samples/ratelimit/) IP rate-limiting diff --git a/docs/tr-TR/development/server/migration.md b/docs/tr-TR/development/server/migration.md new file mode 100644 index 000000000..eb7b3e341 --- /dev/null +++ b/docs/tr-TR/development/server/migration.md @@ -0,0 +1,50 @@ +# Database Migration + +There may be some incompatible changes during the plugin update iteration, these incompatible upgrade scripts can be handled by writing migration files. + +## How to add a migration file? + +```ts +export class MyPlugin extends Plugin { + load() { + // Load a single Migration file + this.db.addMigration(); + // Load multiple Migration files + this.db.addMigrations(); + } +} +``` + +API reference. + +- [db.addMigration()](/api/database#addmigration) +- [db.addMigrations()](/api/database#addmigrations) + +## When to execute? + +```bash +## When the app is upgraded, migrator.up() and db.sync() are executed +yarn nocobase upgrade +# Trigger migration separately +yarn nocobase migrator up +``` + +## When do I need to write a migration file? + +It is usually used to update the system configuration stored in the database during the upgrade process. If it is just a collection configuration change, you don't need to configure migration, just execute ``yarn nocobase db:sync`` to synchronize to the database. + +## Migration file + +```ts +import { Migration } from '@nocobase/server'; + +export default class CustomMigration extends Migration { + async up() { + // + } + + async down() { + // + } +} +``` diff --git a/docs/tr-TR/development/server/resources-actions.md b/docs/tr-TR/development/server/resources-actions.md new file mode 100644 index 000000000..1d79b0718 --- /dev/null +++ b/docs/tr-TR/development/server/resources-actions.md @@ -0,0 +1,464 @@ +# Resources and Actions + +In the web development world, you may have heard of the concept of RESTful, and NocoBase borrows this concept of resources to map various entities in the system, such as data in a database, a file in a file system or a service. However, NocoBase does not fully follow the RESTful conventions based on practical considerations, but rather extends the specifications from the [Google Cloud API Design Guide](https://cloud.google.com/apis/design) to fit more scenarios. + +## Basic concepts + +The same concept as resources in RESTful, which are externally available objects in the system that can be manipulated, such as data tables, files, and other custom objects. + +Actions refer to reading and writing to resources, usually for accessing data, creating data, updating data, deleting data, etc. NocoBase implements access to resources by defining actions, the core of which is actually a Koa-compatible middleware function for handling requests. + +### Automatic mapping of collections to resources + +NocoBase automatically maps collections to resources by default, and also provides a server-side data interface. So by default, as long as a collection is defined using `db.collection()`, you can access the data resources of this collection via NocoBase HTTP API. The name of the automatically generated resource is the same as the collection name, for example, the collection defined by `db.collection({ name: 'users' })` has the corresponding resource name `users`. + +Also, there are built-in common CRUD actions for these data resources, and built-in actions methods for associative data for relational data resources. + +The default actions for a simple data resource: + +* [`list`](/api/actions#list): Query the list of data in the collection +* [`get`](/api/actions#get): Query a single record in the collection +* [`create`](/api/actions#create): Create a single record to the collection +* [`update`](/api/actions#update): Update a single record on the collection +* [`destroy`](/api/actions#destroy): Delete a single record from the collection + +In addition to simple CRUD actions, relational resources have default relational actions: + +* [`add`](/api/actions#add): Add a association to the data +* [`remove`](/api/actions#remove): Removes an association from the data +* [`set`](/api/actions#set): Set the association to the data +* [`toggle`](/api/actions#toggle): Add or remove associations to data + +For example, to define an article collection and synchronize it to the database. + +```ts +app.db.collection({ + name: 'posts', + fields: [ + { type: 'string', name: 'title' } + ] +}); + +await app.db.sync(); +``` + +All CRUD methods for the `posts` data resource can then be called directly via the HTTP API: ```bash + +```bash +# create +curl -X POST -H "Content-Type: application/json" -d '{"title": "first"}' http://localhost:13000/api/posts:create +# list +curl http://localhost:13000/api/posts:list +# update +curl -X PUT -H "Content-Type: application/json" -d '{"title": "second"}' http://localhost:13000/api/posts:update +# destroy +curl -X DELETE http://localhost:13000/api/posts:destroy?filterByTk=1 +``` + +### Customize Actions + +It is also possible to extend specific resources with more actions when the default provided actions such as CRUD do not satisfy the business scenario. For example, additional processing of built-in actions, or the need to set default parameters. + +Custom actions for specific resources, such as overriding the `create` action in the article collection. + +```ts +// Equivalent to app.resourcer.registerActions() +// Register the create action method for article resources +app.actions({ + async ['posts:create'](ctx, next) { + const postRepo = ctx.db.getRepository('posts'); + await postRepo.create({ + values: { + ... . ctx.action.params.values, + // restrict the current user to be the creator of the post + userId: ctx.state.currentUserId + } + }); + + await next(); + } +}); +``` + +This adds a reasonable restriction in the business that users cannot create articles as other users. + +Custom operations for all global resources, such as adding `export` action to all collections. + +```ts +app.actions({ + // Add export method to all resources for exporting data + async export(ctx, next) { + const repo = ctx.db.getRepository(ctx.action.resource); + const results = await repo.find({ + filter: ctx.action.params.filter + }); + ctx.type = 'text/csv'; + // Splice to CSV format + ctx.body = results + .map(row => Object.keys(row) + .reduce((arr, col) => [... . arr, row[col]], []).join(',') + ).join('\n'); + + next(); + } +}); +``` + +The data in CSV format can then be exported as follows from the HTTP API. + +```bash +curl http://localhost:13000/api/:export +``` + +### Action parameters + +Once the client's request reaches the server, the relevant request parameters are parsed by rule and placed on the request's `ctx.action.params` object. there are three main sources for Action parameters. + +1. default parameters at the time of Action definition +2. carried by the client request +3. other middleware preprocessing + +The parameters from these three parts are combined in this order and eventually passed into the action's execution function before being processed by the real action handler. This is also true in multiple middleware, where the parameters from the previous middleware are continued to be passed to the next middleware with `ctx`. + +The parameters available for built-in actions can be found in the [@nocobase/actions](/api/actions) package. Except for custom actions, client requests mainly use these parameters, and custom actions can be extended with the required parameters according to business requirements. + +Middleware preprocessing mainly uses the `ctx.action.mergeParams()` method and has different merge strategies depending on the parameter types, see also the [mergeParams()](/api/resourcer/action#mergeparams) method for details. + +The default parameters of the built-in Action can only be executed with the `mergeParams()` method for each parameter's default policy when merging, in order to achieve the purpose of limiting certain operations on the server side. For example + +```ts +app.resource({ + name: 'posts', + actions: { + create: { + whitelist: ['title', 'content'], + blacklist: ['createdAt', 'createdById'], + } + } +}); +``` + +The above defines the `create` action for the `posts` resource, where `whitelist` and `blacklist` are whitelisted and blacklisted respectively for the `values` parameter, i.e. only the `title` and `content` fields in the `values` parameter are allowed, and the ` createdAt` and `createdById` fields in the `values` parameter are disabled. + +### Custom resources + +Data-based resources are also divided into standalone resources and association resources. + +* Standalone resources: `` +* Association resources: `. ` + +```ts +// Equivalent to app.resourcer.define() + +// Define article resources +app.resource({ + name: 'posts' +}); + +// Define the article's author resource +app.resource({ + name: 'posts.user' +}); + +// Define the article's comment resource +app.resource({ + name: 'posts.coments' +}); +``` + +The cases where customization is needed are mainly for non-database table-like resources, such as in-memory data, proxy interfaces for other services, etc., and for cases where specific actions need to be defined for existing table-like resources. + +For example, to define a database-independent resource that sends a notification action. + +```ts +app.resource({ + name: 'notifications', + actions: { + async send(ctx, next) { + await someProvider.send(ctx.request.body); + next(); + } + } +}); +``` + +Then it can be accessed in the HTTP API as follows + +```bash +curl -X POST -d '{"title": "Hello", "to": "hello@nocobase.com"}' 'http://localhost:13000/api/notifications:send' +``` + +## Example + +Let's continue the simple store scenario from the previous [Collections and fields example](/development/server/collections-fields#Example) to further understand the concepts related to resources and actions. It is assumed here that we base further resource and action definitions on the previous collection's example, so the definition of collection is not repeated here. + +As long as the corresponding collections are defined, we can use default actions directly for data resources such as products, orders, etc. in order to complete the most basic CRUD scenarios. + +### Overriding default actions + + +Sometimes, there are operations that are not simply for a single record, or the parameters of the default actions need to have some control, we can override the default actions. For example, when we create an order, instead of the client submitting `userId` to represent the ownership of the order, the server should determine the ownership of the order based on the currently logged-in user, so we can override the default `create` action. For simple extensions, we write directly in the main class of the plugin. + +```ts +import { Plugin } from '@nocobase/server'; +import actions from '@nocobase/actions'; + +export default class ShopPlugin extends Plugin { + async load() { + // ... + this.app.resource({ + name: 'orders', + actions: { + async create(ctx, next) { + ctx.action.mergeParams({ + values: { + userId: ctx.state.user.id + } + }); + + return actions.create(ctx, next); + } + } + }); + } +} +``` + +In this way, we override the default `create` action for order data resources during plugin loading, but the default logic is still called after modifying the action parameters, so there is no need to write it yourself. The `mergeParams()` method that modifies the submit parameters is useful for the built-in default actions, which we will describe later. + +### Custom actions for collection resources + +When the built-in actions do not meet the business needs, we can extend the functionality of the resource by customizing the actions. For example, usually an order will have many statuses, if we design the values of the `status` field as a series of enumerated values. + +* `-1`: cancelled +* `0`: order placed, not paid +* `1`: Paid, not shipped +* `2`: shipped, not signed +* `3`: signed, order completed + +Then we can realize the change of order status through custom actions, such as a shipping action on the order. Although the simple case can be realized through the `update` action, if there are more complicated cases such as payment and signing, using only `update` will cause the problem of unclear semantics and confusing parameters, so we can realize it through custom actions. + +First we add a definition of a shipping information collection, saved to `collections/deliveries.ts`. + +```ts +export default { + name: 'deliveries', + fields: [ + { + type: 'belongsTo', + name: 'order' + }, + { + type: 'string', + name: 'provider' + }, + { + type: 'string', + name: 'trackingNumber' + }, + { + type: 'integer', + name: 'status' + } + ] +}; +``` + +Also extend the orders collection with an associated field for shipping information (`collections/orders.ts`). + +```ts +export default { + name: 'orders', + fields: [ + // ... . other fields + { + type: 'hasOne', + name: 'delivery' + } + ] +}; +``` + +Then we add the corresponding action definition in the main class of the plugin: + +```ts +import { Plugin } from '@nocobase/server'; + +export default class ShopPlugin extends Plugin { + async load() { + // ... + this.app.resource({ + name: 'orders', + actions: { + async deliver(ctx, next) { + const { filterByTk } = ctx.action.params; + const orderRepo = ctx.db.getRepository('orders'); + + const [order] = await orderRepo.update({ + filterByTk, + values: { + status: 2, + delivery: { + ... . ctx.action.params.values, + status: 0 + status: 0 } + } + }); + + ctx.body = order; + + next(); + } + } + }); + } +} +``` + +The Repository uses the data repository class of collection, from which most of the data reading and writing actions are done, see the [Repository API](/api/database/repository) section for details. + +Once defined, we can call the "ship" action from the client via the HTTP API: + +```bash +curl \ + -X POST \ + -H 'Content-Type: application/json' \ + -d '{"provider": "SF", "trackingNumber": "SF1234567890"}' \ + '/api/orders:deliver/' +``` + +Similarly, we can define more similar actions, such as payment, signup, etc. + +### Parameter merging + +Suppose we want to allow users to query their own and only their own orders, and we need to restrict them from querying cancelled orders, then we can define with the default parameters of the action. + +```ts +import { Plugin } from '@nocobase/server'; + +export default class ShopPlugin extends Plugin { + async load() { + // ... + this.app.resource({ + name: 'orders', + actions: { + // default parameters for list actions + list: { + filter: { + // Filter operator extended by the users plugin + $isCurrentUser: true, + status: { + $ne: -1 + } + }, + fields: ['id', 'status', 'createdAt', 'updatedAt'] + } + } + }); + } +} +``` + +When the user queries from the client, additional parameters can also be added to the requested URL, such as + +```bash +curl 'http://localhost:13000/api/orders:list?productId=1&fields=id,status,quantity,totalPrice&appends=product' +``` + +The actual query criteria will be combined as + +```json +{ + "filter": { + "$and": { + "$isCurrentUser": true, + "status": { + "$ne": -1 + }, + "productId": 1 + } + }, + "fields": ["id", "status", "quantity", "totalPrice", "createdAt", "updatedAt"], + "appends": ["product"] +} +``` + +and get the expected query results. + +Alternatively, if we need to restrict the interface for creating orders to fields such as order number (`id`), total price (`totalPrice`), etc. that cannot be submitted by the client, this can be controlled by defining default parameters for the `create` action as follows + +```ts +import { Plugin } from '@nocobase/server'; + +export default class ShopPlugin extends Plugin { + async load() { + // ... + this.app.resource({ + name: 'orders', + actions: { + create: { + blacklist: ['id', 'totalPrice', 'status', 'createdAt', 'updatedAt'], + values: { + status: 0 + } + } + } + }); + } +} +``` + +This way, even if the client intentionally submits these fields, they will be filtered out and will not exist in the `ctx.action.params` parameter set. + +If there are more complex restrictions, such as only being able to place an order if the item is on the shelf and in stock, this can be achieved by configuring the middleware to + +```ts +import { Plugin } from '@nocobase/server'; + +export default class ShopPlugin extends Plugin { + async load() { + // ... + this.app.resource({ + name: 'orders', + actions: { + create: { + middlewares: [ + async (ctx, next) => { + const { productId } = ctx.action.params.values; + + const product = await ctx.db.getRepository('products').findOne({ + filterByTk: productId, + filter: { + enabled: true, + inventory: { + $gt: 0 + } + } + }); + + if (!product) { + return ctx.throw(404); + } + + await next(); + } + ] + } + } + }); + } +} +``` + +Putting some of the business logic (especially the preprocessing) into middleware makes our code clearer and easier to maintain. + +## Summary + +With the above example we have described how to define resources and related actions. To review this chapter. + +* Automatic mapping of collections to resources +* Built-in default resource actions +* Custom actions on resources +* Parameter merging order and strategy for operations + +The code covered in this chapter is included in a complete sample package [packages/samples/shop-actions](https://github.com/nocobase/nocobase/tree/main/packages/samples/shop-actions ), which can be run directly locally to see the results. diff --git a/docs/tr-TR/development/server/test.md b/docs/tr-TR/development/server/test.md new file mode 100644 index 000000000..0359876db --- /dev/null +++ b/docs/tr-TR/development/server/test.md @@ -0,0 +1,162 @@ +# Testing + +The tests are based on the [Jest](https://jestjs.io/) framework. To facilitate writing tests, `mockDatabase()` and `mockServer()` are provided for database and server-side application testing. + +## `mockDatabase()` + +provides a fully isolated db testing environment by default + +```ts +import { mockDatabase } from '@nocobase/test'; + +describe('my db suite', () => { + let db; + + beforeEach(async () => { + db = mockDatabase(); + db.collection({ + name: 'posts', + fields: [ + { + type: 'string', + name: 'title', + } + ] + }); + await db.sync(); + }); + + afterEach(async () => { + await db.close(); + }); + + test('my case', async () => { + const repository = db.getRepository('posts'); + const post = await repository.create({ + values: { + title: 'hello' + } + }); + + expect(post.get('title')).toEqual('hello'); + }); +}); +``` + +## `mockServer()` + +provides a mock server-side application instance, corresponding to app.db as a `mockDatabase()` instance, and also provides a convenient `app.agent()` for testing the HTTP API, and wraps `app.agent().resource()` for the Resource Action of NocoBase for testing the Action. + +```ts +import { mockServer } from '@nocobase/test'; + +class MyPlugin extends Plugin { + +} + +describe('my suite', () => { + let app; + let agent; + + beforeEach(async () => { + app = mockServer(); + agent = app.agent(); + // Add the plugins to be registered + app.plugin(MyPlugin, { name: 'my-plugin' }); + // Load the configuration + app.load(); + // Clear the database and install + app.install({ clean: true }); + }); + + afterEach(async () => { + await app.destroy(); + }); + + test('my case', async () => { + await agent.resource('posts').create({ + values: { + title: 'hello' + } + }); + await agent.get('/users:check').set({ Authorization: 'Bearer abc' }); + }); +}); +``` + +## Example + +Let's write a test of the plugin using in previous chapter [Resources and Actions](development/server/resources-actions) as an example. + +```ts +import { mockServer } from '@nocobase/test'; +import Plugin from '... /... /src/server'; + +describe('shop actions', () => { + let app; + let agent; + let db; + + beforeEach(async () => { + app = mockServer(); + app.plugin(Plugin); + agent = app.agent(); + db = app.db; + + await app.load(); + await db.sync(); + }); + + afterEach(async () => { + await app.destroy(); + }); + + test('product order case', async () => { + const { body: product } = await agent.resource('products').create({ + values: { + title: 'iPhone 14 Pro', + price: 7999, + enabled: true, + inventory: 1 + } + }); + expect(product.data.price).toEqual(7999); + + const { body: order } = await agent.resource('orders').create({ + values: { + productId: product.data.id + } + }); + expect(order.data.totalPrice).toEqual(7999); + expect(order.data.status).toEqual(0); + + const { body: deliveredOrder } = await agent.resource('orders').deliver({ + filterByTk: order.data.id, + values: { + provider: 'SF', + trackingNumber: '123456789' + } + }); + expect(deliveredOrder.data.status).toBe(2); + expect(deliveredOrder.data.delivery.trackingNumber).toBe('123456789'); + }); +}); +``` + +Once finished, allow the test command on the command line: + +```bash +yarn test packages/samples/shop-actions +``` + +This test will verify that: + +1. products can be created successfully. +2. orders can be created successfully. +3. orders can be shipped successfully. + +Of course this is just a minimal example, not perfect business-wise, but as an example it can already illustrate the whole testing process. + +## Summary + +The sample code covered in this chapter is integrated in the corresponding package [packages/samples/shop-actions](https://github.com/nocobase/nocobase/tree/main/packages/samples/shop-actions), which can be run directly locally to see the results. diff --git a/docs/tr-TR/development/your-fisrt-plugin.md b/docs/tr-TR/development/your-fisrt-plugin.md new file mode 100644 index 000000000..61d9755e5 --- /dev/null +++ b/docs/tr-TR/development/your-fisrt-plugin.md @@ -0,0 +1,125 @@ +# Develop the first plugin + +Before start, you need to install NocoBase:. + +- [create-nocobase-app installation](/welcome/getting-started/installation/create-nocobase-app) +- [Git source installation](/welcome/getting-started/installation/git-clone) + +Once NocoBase is installed, we can start our plugin development journey. + +## Create a plugin + +First, you can quickly create an empty plugin via the CLI with the following command. + +```bash +yarn pm create hello +``` + +The directory where the plugin is located ``packages/plugins/hello`` and the plugin directory structure is + +```bash +|- /hello + |- /src + |- /client # plugin client code + |- /server # plugin server code + |- client.d.ts + |- client.js + |- package.json # plugin package information + |- server.d.ts + |- server.js +package.json + +package.json information + +```json +{ + "name": "@nocobase/plugin-hello", + "version": "0.1.0", + "main": "lib/server/index.js", + "devDependencies": { + "@nocobase/client": "0.8.0-alpha.1", + "@nocobase/test": "0.8.0-alpha.1" + } +} +``` + +A NocoBase plugin is also an NPM package, the correspondence rule between plugin name and NPM package name is `${PLUGIN_PACKAGE_PREFIX}-${pluginName}`. + +`PLUGIN_PACKAGE_PREFIX` is the plugin package prefix, which can be customized in .env, [click here for PLUGIN_PACKAGE_PREFIX description](/api/env#plugin_package_prefix). + +## Code the plugin + +Look at the `packages/plugins/hello/src/server/plugin.ts` file and modify it to + +```ts +import { InstallOptions, Plugin } from '@nocobase/server'; + +export class HelloPlugin extends Plugin { + afterAdd() {} + + beforeLoad() {} + + async load() { + this.db.collection({ + name: 'hello', + fields: [ + { type: 'string', name: 'name' } + ], + }); + this.app.acl.allow('hello', '*'); + } + + async install(options?: InstallOptions) {} + + async afterEnable() {} + + async afterDisable() {} + + async remove() {} +} + +export default HelloPlugin; +``` + +## Register the plugin + +```bash +yarn pm add hello +``` + +## Activate the plugin + +When the plugin is activated, the hello table that you just configured is automatically created. + +```bash +yarn pm enable hello +``` + +## Start the application + +```bash +# for development +yarn dev + +# for production +yarn build +yarn start +``` + +## Experience the plugin + +Insert data into the hello table of the plugin + +```bash +curl --location --request POST 'http://localhost:13000/api/hello:create' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "name": "Hello world" +}' +``` + +View the data + +```bash +curl --location --request GET 'http://localhost:13000/api/hello:list' +``` diff --git a/docs/tr-TR/index.md b/docs/tr-TR/index.md new file mode 100644 index 000000000..ab11f4321 --- /dev/null +++ b/docs/tr-TR/index.md @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/tr-TR/manual/blocks-guide/charts.md b/docs/tr-TR/manual/blocks-guide/charts.md new file mode 100644 index 000000000..e349de5a5 --- /dev/null +++ b/docs/tr-TR/manual/blocks-guide/charts.md @@ -0,0 +1,201 @@ +# Charts + +Currently, chart blocks in NocoBase need to be implemented via a configuration file or by writing code. The chart library uses [g2plot](https://g2plot.antv.vision/en/examples/gallery), which theoretically supports all charts on [https://g2plot.antv.vision/en/examples/gallery](https://g2plot.antv.vision/en/examples/gallery). The currently configurable charts include + +- Column charts +- Bar charts +- Line charts +- Pie charts +- Area charts + +## Add and edit charts + +![chart-edit.gif](./charts/chart-edit.gif) + +## Chart Configuration + +The initial chart configuration is static JSON data + +```json +{ + "data": [ + { + "type": "furniture & appliances", + "sales": 38 + }, + { + "type": "食品油副食", + "sales": 52 + }, + { + "type": "Fresh Fruit", + "sales": 61 + }, + { + "type": "美容洗护", + "sales": 145 + }, + { + "type": "Maternity & Baby Products", + "sales": 48 + }, + { + "type": "Imported Food", + "sales": 38 + }, + { + "type": "Food & Beverage", + "sales": 38 + }, + { + "type": "Home Cleaning", + "sales": 38 + } + ], + "xField": "type", + "yField": "sales", + "label": { + "position": "middle", + "style": { + "fill": "#FFFFFF", + "opacity": 0.6 + } + }, + "xAxis": { + "label": { + "autoHide": true, + "autoRotate": false + } + }, + "meta": { + "type": { + "alias": "category" + }, + "sales": { + "alias": "sales" + } + } +} + +``` + +Data supports expression, NocoBase has a built-in `requestChartData(config)` function for custom chart data requests. Parameters are described in: [https://github.com/axios/axios#request-config](https://github.com/axios/axios#request-config) + +Example. + +```json +{ + "data": "{{requestChartData({ url: 'collectionName:getColumnChartData' })}}", + "xField": "type", + "yField": "sales", + "label": { + "position": "middle", + "style": { + "fill": "#FFFFFF", + "opacity": 0.6 + } + }, + "xAxis": { + "label": { + "autoHide": true, + "autoRotate": false + } + }, + "meta": { + "type": { + "alias": "category" + }, + "sales": { + "alias": "sales" + } + } +} + +``` + +HTTP API example. + +```bash +GET /api/collectionName:getColumnChartData + +Response Body +{ + "data": [ + { + "type": "furniture & appliances", + "sales": 38 + }, + { + "type": "食品油副食", + "sales": 52 + }, + { + "type": "Fresh Fruit", + "sales": 61 + }, + { + "type": "美容洗护", + "sales": 145 + }, + { + "type": "Maternity & Baby Products", + "sales": 48 + }, + { + "type": "Imported Food", + "sales": 38 + }, + { + "type": "Food & Beverage", + "sales": 38 + }, + { + "type": "Home Cleaning", + "sales": 38 + } + ] +} + +``` + +## Server-side implementation + +Add a custom getColumnChartData method to the data table named collectionName. + +```js +app.resourcer.registerActionHandlers({ + 'collectionName:getColumnChartData': (ctx, next) => { + // The data to be output + ctx.body = []; + await next(); + }, +}); + +``` + +## Video + +### Static data +https://user-images.githubusercontent.com/1267426/198877269-1c56562b-167a-4808-ada3-578f0872bce1.mp4 + +### Dynamic data +https://user-images.githubusercontent.com/1267426/198877336-6bd85f0b-17c5-40a5-9442-8045717cc7b0.mp4 + +### More charts + +Theoretically supports all charts on [https://g2plot.antv.vision/en/examples/gallery](https://g2plot.antv.vision/en/examples/gallery) + +https://user-images.githubusercontent.com/1267426/198877347-7fc2544c-b938-4e34-8a83-721b3f62525e.mp4 + +## JS Expressions + +Syntax + +```js +{ + "key1": "{{ js expression }}" +} +``` + +https://user-images.githubusercontent.com/1267426/198877361-808a51cc-6c91-429f-8cfc-8ad7f747645a.mp4 + diff --git a/docs/tr-TR/manual/blocks-guide/charts/chart-edit.gif b/docs/tr-TR/manual/blocks-guide/charts/chart-edit.gif new file mode 100644 index 000000000..34227f45f Binary files /dev/null and b/docs/tr-TR/manual/blocks-guide/charts/chart-edit.gif differ diff --git a/docs/tr-TR/manual/core-concepts/a-b-c.md b/docs/tr-TR/manual/core-concepts/a-b-c.md new file mode 100644 index 000000000..d1b6e9951 --- /dev/null +++ b/docs/tr-TR/manual/core-concepts/a-b-c.md @@ -0,0 +1,21 @@ +# A·B·C + +At the no-code level, the core concept of NocoBase can be summarized as `A·B·C`. + +`A·B·C` stands for `Action·Block·Collection`. We design data structure by `Collection`, organize and display data by `Block`, and interact with data by `Action`. + +## **Separate "data structure" and "user interface"** + +When defining data, focus on defining data; when defining views, focus on defining views. + +Abstract the business by defining the data; then define blocks to organize the content to present the data in the way you want. + +## **One Data table, Many Presentations** + +Abstract a unified data model for the business, and then with blocks you can build a variety of presentations for the same data table for different scenarios, different roles, and different combinations. + +## **Driven by Action** + +`Collection`defines the structure of the data, and the `Block`organize the presentation of the data. So, what drives data interactions and changes? The answer is `Action`. + +`Block`present the data to the user, and `Action`send the user's instructions to the server to complete the interaction or change of the data. diff --git a/docs/tr-TR/manual/core-concepts/actions.md b/docs/tr-TR/manual/core-concepts/actions.md new file mode 100644 index 000000000..b4551a486 --- /dev/null +++ b/docs/tr-TR/manual/core-concepts/actions.md @@ -0,0 +1,31 @@ +# Actions + +An `action` is a collection of actions that accomplish a specific goal. An `action` is used in NocoBase to process data or communicate with the server. Actions are usually triggered by clicking a button. + +## Action types + +NocoBase currently supports more than 10 types of actions, and more can be supported in the future by way of plugins. + +| Name | Description | +| --- | --- | +| Filter | Specifies the range of data to be displayed | +| Add | Opens a popup window for adding new data, which usually contains a form block. | +| View | Opens a popup window to view the specified data, which usually contains a detail block. | +| Edit | Opens a popup window to modify the specified data, which usually contains a form block. | +| Delete | Opens a dialog box to delete the specified data, and then delete it after confirmation. | +| Export | Exports data to Excel, often combined with filtering. | +| Print | Opens a browser print window to print the specified data, often combined with a detail block. | +| Submit | Submit the data of the specified form block to the server. | +| Refresh | Refreshes the data in the current block. | +| Import | Import data from an Excel template | +| Bulk Edit | Batch Edit Data | +| Bulk Update | Batch Update Data | +| Popup | Open a popup window or drawer in which you can place blocks | +| Update record | Automatically update specified fields when clicked | +| Customize request | Send requests to third parties | + +## Configure actions + +In UI Editor mode, move the mouse over an action button and the configuration items supported by that action will appear in the upper right corner. For example, for the filter action. + +![action-config-5.jpg](./actions/action-config-5.jpg) \ No newline at end of file diff --git a/docs/tr-TR/manual/core-concepts/actions/action-config-5.jpg b/docs/tr-TR/manual/core-concepts/actions/action-config-5.jpg new file mode 100644 index 000000000..8b91de132 Binary files /dev/null and b/docs/tr-TR/manual/core-concepts/actions/action-config-5.jpg differ diff --git a/docs/tr-TR/manual/core-concepts/blocks.md b/docs/tr-TR/manual/core-concepts/blocks.md new file mode 100644 index 000000000..ed694e978 --- /dev/null +++ b/docs/tr-TR/manual/core-concepts/blocks.md @@ -0,0 +1,86 @@ +# Blocks + +Blocks are views used to display and manipulate data. In NocoBase, pages, popups and drawers are considered as containers of blocks, and the container is like a canvas in which various blocks can be placed. + +Thanks to NocoBase's design of separating data and view, pages carry data through blocks and organize and manage data in different forms according to different block types. + +## Structure of blocks + +A block consists of three parts. + +1. content area: the body of the block +2. action area: various action buttons can be placed to manipulate the block data +3. configuration area: buttons for operating the block configuration + +![6.block.jpg](./blocks/6.block.jpg) + +## Block types + +![add-block.jpg](./blocks/add-block.jpg) + +NocoBase currently has 10+ types of blocks built in, more can be supported in the future by way of plugins. + +- **Data blocks:** blocks designed for organizing data. + - **Table:** A block that present multiple data in a table, either a single collection or multiple collections that are related to each other. + - **Form:** A block for entering or editing data in a form, either for a particular collection or for multiple collections that are related to each other in a unified way. + - **Details:** A block to display a specific record, either for a particular collection or for multiple collection that are related to each other. + - **Calendar:** A block that displays multiple records in the form of a calendar, suitable for certain data with important characteristics in terms of date. + - **Kanban:** A block that displays multiple data in the form of a Kanban board, suitable for managing production processes. +- **Chart blocks:** Blocks designed for graphical presentation of statistical data. Currently supports: bar graphs, bar charts, line graphs, pie charts, area charts, etc. +- **Other blocks:** Blocks designed to display special data. + - **Markdown:** Text content written in Markdown. + - **Audit Log**: Show the change records of all data in a collection, including new, edit and delete. + +## Add block + +Enter the UI Editor mode and click the Add block button on the page and in the pop-up window to add the block. The options are divided into 4 steps. + +1. Select block type: Currently available block types include Table, Form, Details, Calendar, Kanban, Markdown +2. Select Collection: All collections will be listed here +3. Choose the creation method: create a blank block, or duplicate a block template , or reference a block template +4. Select Template: If you selected Create from Template in step 3, select the template in step 4 + +![6.block-add.jpg](./blocks/6.block-add.jpg) + +## Configure Blocks + +The configuration of blocks consists of three elements. + +- Configure block content +- Configure block actions +- Configure block properties + +### Configure block content + +Take the table block as an example, the content of the block is the columns to be displayed in the table. Click Configure columns to configure the columns to be displayed. + +![6.block-content.gif](./blocks/6.block-content.gif) + +### Configure block actions + +Take table block as an example, there are filter, add, delete, view, edit, customize and other actions available. Click the Configure actions button to configure the actions. Each of the action buttons can be configured for their own properties. + +![6.block-content.gif](./blocks/6.block-content%201.gif) + +### Configure block properties + +Move the cursor to the upper right corner of the block and you will see the block configuration button. Using the table block as an example, the following properties can be configured. + +- Drag & drop sorting +- Set the data scope +- Set default sorting rules +- Records per page + +![6.collection-setting.gif](./blocks/6.collection-setting.gif) + +## Adjust the layout + +It is possible to put either just one block or multiple blocks in combination within the page. You can adjust the position and width of the blocks by dragging and dropping them. + +![block-drag.gif](./blocks/block-drag.gif) + +## Block templates + +You can save a block as a template, which can be copied or referenced later. + +For example, if a form is used for both adding and editing data, then you can save this form as a template and reference it in the Add Data and Edit Data blocks. \ No newline at end of file diff --git a/docs/tr-TR/manual/core-concepts/blocks/6.block-add.jpg b/docs/tr-TR/manual/core-concepts/blocks/6.block-add.jpg new file mode 100644 index 000000000..f84489669 Binary files /dev/null and b/docs/tr-TR/manual/core-concepts/blocks/6.block-add.jpg differ diff --git a/docs/tr-TR/manual/core-concepts/blocks/6.block-content 1.gif b/docs/tr-TR/manual/core-concepts/blocks/6.block-content 1.gif new file mode 100644 index 000000000..9c4c1bb57 Binary files /dev/null and b/docs/tr-TR/manual/core-concepts/blocks/6.block-content 1.gif differ diff --git a/docs/tr-TR/manual/core-concepts/blocks/6.block-content.gif b/docs/tr-TR/manual/core-concepts/blocks/6.block-content.gif new file mode 100644 index 000000000..9c4c1bb57 Binary files /dev/null and b/docs/tr-TR/manual/core-concepts/blocks/6.block-content.gif differ diff --git a/docs/tr-TR/manual/core-concepts/blocks/6.block.jpg b/docs/tr-TR/manual/core-concepts/blocks/6.block.jpg new file mode 100644 index 000000000..b1521e61c Binary files /dev/null and b/docs/tr-TR/manual/core-concepts/blocks/6.block.jpg differ diff --git a/docs/tr-TR/manual/core-concepts/blocks/6.collection-setting.gif b/docs/tr-TR/manual/core-concepts/blocks/6.collection-setting.gif new file mode 100644 index 000000000..0031c2f5b Binary files /dev/null and b/docs/tr-TR/manual/core-concepts/blocks/6.collection-setting.gif differ diff --git a/docs/tr-TR/manual/core-concepts/blocks/add-block.jpg b/docs/tr-TR/manual/core-concepts/blocks/add-block.jpg new file mode 100644 index 000000000..8ea520ee1 Binary files /dev/null and b/docs/tr-TR/manual/core-concepts/blocks/add-block.jpg differ diff --git a/docs/tr-TR/manual/core-concepts/blocks/block-drag.gif b/docs/tr-TR/manual/core-concepts/blocks/block-drag.gif new file mode 100644 index 000000000..20e861005 Binary files /dev/null and b/docs/tr-TR/manual/core-concepts/blocks/block-drag.gif differ diff --git a/docs/tr-TR/manual/core-concepts/collections.md b/docs/tr-TR/manual/core-concepts/collections.md new file mode 100644 index 000000000..3defda4cb --- /dev/null +++ b/docs/tr-TR/manual/core-concepts/collections.md @@ -0,0 +1,60 @@ +# Collections + +Before developing a system, we usually have to abstract the business and build a data model. It's called collections in NocoBase. Collections in NocoBase consists of fields (columns) and records (rows). The concept of a collection is similar to the concept of a data table in a relational database, but the concept of fields is slightly different. + +For example, in a collection describing an order, each column contains information about a specific attribute of the order, such as the delivery address, while each row contains all the information about a specific order, such as order number, customer name, phone number, delivery address, etc. + +## Separate "data structure" and "user interface" + +NocoBase's `Data` and `View` are separated, managed and presented by `Collections` and `Blocks` respectively. + +This means that, + +- you can create **one collection** and design **one set of interfaces** for it, to enable the presentation and manipulation of data. +- You can also create **one collection** and design **many sets of interfaces** for it, for the presentation and manipulation of data in different scenarios or roles. +- You can also create **multiple collections** and then design **one set of interfaces** for them to display and manipulate multiple data tables at the same time. +- You can even create **multiple collections** and then design **multiple sets of interfaces** for them, each of which can operate on multiple data tables and perform unique functions. + +Simply put, the separation of data and interfaces makes **the organization and management of data more flexible**, and how you present the data depends on how you configure the interfaces. + +## Field Types + +NocoBase currently supports the following dozens of fields, and more can be supported in the future by way of plug-ins. + +| Name | Type | +| --- | --- | +| single-line text | basic type | +| Icon | Basic Type | +| Multi-line text | Basic type | +| Password | Basic type | +| Mobile Number | Basic Type | +| Number | Basic Type | +| Integer | Basic Type | +| Email | Basic Type | +| Percent | Basic Type | +| Drop-down menu (single selection) | Select type | +| Drop-down menu (multiple choice) | Select type | +| China Administrative Region | Select Type | +| Check | Select Type | +| Radio | Select Type | +| Checkbox | Select Type | +| Link to | Relationship Type | +| One-to-One (has one) | Relationship Type | +| One-to-One (belongs to) | Relationship Type | +| One-to-many | Relationship Type | +| Many-to-one | relationship type | +| Many-to-many | relationship type | +| Formula | advanced type | +| AutoCoding | Advanced Types | +| JSON | Advanced Types | +| Markdown | Multimedia | +| Rich Text | Multimedia | +| Attachments | Multimedia | +| Date | Date & Time | +| Time | Date & Time | +| ID | System Information | +| Created by | System Information | +| Date Created | System Information | +| Last Modified By | System Information | +| Last Modified Date | System Information | +| Formula | Advanced Type | diff --git a/docs/tr-TR/manual/core-concepts/containers.md b/docs/tr-TR/manual/core-concepts/containers.md new file mode 100644 index 000000000..e89102fb8 --- /dev/null +++ b/docs/tr-TR/manual/core-concepts/containers.md @@ -0,0 +1,23 @@ +# Containers + +Pages, popups, and drawers are considered as containers of blocks in NocoBase. The container is like a canvas in which various blocks can be placed. + +## Pages + +![container-page.jpg](./containers/container-page.jpg) + +## Popups + +![container-dialog.jpg](./containers/container-dialog.jpg) + +## Drawers + +![container-drawer.jpg](./containers/container-drawer.jpg) + +## Tabs are supported inside containers + +Multiple tabs can be added within popups, drawers, and pages. Add different blocks to each tab to display different content and actions. For example, in a customer information popup, add 3 tabs to display customer's personal information, order history, customer reviews. + +![7.tabs.gif](./containers/7.tabs.gif) + +![container-tab-2.jpg](./containers/container-tab-2.jpg) diff --git a/docs/tr-TR/manual/core-concepts/containers/7.tabs.gif b/docs/tr-TR/manual/core-concepts/containers/7.tabs.gif new file mode 100644 index 000000000..62f47781d Binary files /dev/null and b/docs/tr-TR/manual/core-concepts/containers/7.tabs.gif differ diff --git a/docs/tr-TR/manual/core-concepts/containers/container-dialog.jpg b/docs/tr-TR/manual/core-concepts/containers/container-dialog.jpg new file mode 100644 index 000000000..a1c8fdf08 Binary files /dev/null and b/docs/tr-TR/manual/core-concepts/containers/container-dialog.jpg differ diff --git a/docs/tr-TR/manual/core-concepts/containers/container-drawer.jpg b/docs/tr-TR/manual/core-concepts/containers/container-drawer.jpg new file mode 100644 index 000000000..51824eb75 Binary files /dev/null and b/docs/tr-TR/manual/core-concepts/containers/container-drawer.jpg differ diff --git a/docs/tr-TR/manual/core-concepts/containers/container-page.jpg b/docs/tr-TR/manual/core-concepts/containers/container-page.jpg new file mode 100644 index 000000000..72c542634 Binary files /dev/null and b/docs/tr-TR/manual/core-concepts/containers/container-page.jpg differ diff --git a/docs/tr-TR/manual/core-concepts/containers/container-tab-2.jpg b/docs/tr-TR/manual/core-concepts/containers/container-tab-2.jpg new file mode 100644 index 000000000..001bf67da Binary files /dev/null and b/docs/tr-TR/manual/core-concepts/containers/container-tab-2.jpg differ diff --git a/docs/tr-TR/manual/core-concepts/menus.md b/docs/tr-TR/manual/core-concepts/menus.md new file mode 100644 index 000000000..b60130b1c --- /dev/null +++ b/docs/tr-TR/manual/core-concepts/menus.md @@ -0,0 +1,54 @@ +# Menus + +The default menu location for NocoBase is at the top and on the left. The top is the first level menu and the left side is the menu for the second level and lower levels. + +Three types of menu items are supported. + +- Menu groups +- Pages +- Links + +Once you enter the UI Editor mode, you can add and edit menus, as well as sort menu items. + +NocoBase currently supports three types of menu items. + +- Page: jumps to the content page the menu item is associated. +- Group: grouping menu items and placing similar menus in a uniform location. +- Link: jumps to a specified URL. + +Take the warehouse system as an example, if you have storage management in your business, storage management contains in and out logs, inventory queries, jump to the ERP application storage and other functions. Then you can set the menu like this. + +``` +- Storage space management (grouping) + - Inventory query (page) + - Inbound and outbound log (page) + - Jump ERP application storage space (link) + +``` + +## Default position + +In NocoBase's built-in page templates, the menu appears at the top and on the left. + +![menu-position.jpg](./menus/menu-position.jpg) + +## Add Menu Item + +![5.menu-add.jpg](./menus/5.menu-add.jpg) + +Click Add menu item to select the type to add. Support infinite level submenu. + +## Configure and Sort + +Move the cursor over the menu item and the Sort and Configure buttons will appear in the upper right corner. Press and hold the Sort button to drag and drop the sorting. + +Configurations that are operable on menu items: + +- Edit +- Move to +- Insert before +- Insert after +- Insert Inner +- Delete + +![menu-move.gif](./menus/menu-move.gif) diff --git a/docs/tr-TR/manual/core-concepts/menus/5.menu-add.jpg b/docs/tr-TR/manual/core-concepts/menus/5.menu-add.jpg new file mode 100644 index 000000000..aa1d1fc8f Binary files /dev/null and b/docs/tr-TR/manual/core-concepts/menus/5.menu-add.jpg differ diff --git a/docs/tr-TR/manual/core-concepts/menus/menu-move.gif b/docs/tr-TR/manual/core-concepts/menus/menu-move.gif new file mode 100644 index 000000000..e5c4beb22 Binary files /dev/null and b/docs/tr-TR/manual/core-concepts/menus/menu-move.gif differ diff --git a/docs/tr-TR/manual/core-concepts/menus/menu-position.jpg b/docs/tr-TR/manual/core-concepts/menus/menu-position.jpg new file mode 100644 index 000000000..b49210ae0 Binary files /dev/null and b/docs/tr-TR/manual/core-concepts/menus/menu-position.jpg differ diff --git a/docs/tr-TR/manual/quick-start/functional-zoning.md b/docs/tr-TR/manual/quick-start/functional-zoning.md new file mode 100644 index 000000000..79c4f4044 --- /dev/null +++ b/docs/tr-TR/manual/quick-start/functional-zoning.md @@ -0,0 +1,9 @@ +# Functional zoning + +NocoBase has a built-in layout template by default, and the interface of this layout template is divided into three main areas. + +1. Configuration entry area. Users with permissions can see the UI Editor, Collections & Fields, Roles and Permissions, block templates, Workflows, and other extended configuration options here. +2. Menu area. At the top is the first level menu, and on the left side are the menus for the second level and lower tiers. Each menu item can be configured as a menu group, page, or link. +3. Block container. This is the block container for the page, in which various blocks can be placed. + +![3.zone.jpg](./functional-zoning/3.zone.jpg) \ No newline at end of file diff --git a/docs/tr-TR/manual/quick-start/functional-zoning/3.zone.jpg b/docs/tr-TR/manual/quick-start/functional-zoning/3.zone.jpg new file mode 100644 index 000000000..12db1d161 Binary files /dev/null and b/docs/tr-TR/manual/quick-start/functional-zoning/3.zone.jpg differ diff --git a/docs/tr-TR/manual/quick-start/plugins.md b/docs/tr-TR/manual/quick-start/plugins.md new file mode 100644 index 000000000..6c53a6f9d --- /dev/null +++ b/docs/tr-TR/manual/quick-start/plugins.md @@ -0,0 +1,13 @@ +# Plugins + +NocoBase is designed as a plugin architecture, and most of the functionality is powered by plugins, except for a lightweight kernel. + +Theoretically, even built-in features can be replaced with new plugins as well. + +## Plugin Manager + +NocoBase provides a visual plugin manager to enable and disable plugins, or to configure them. + +The plugin manager is still in its early stages and in the future hopefully we will find a wide variety of plugins here to meet the needs. + +![plugin-manager.jpg](./plugins/plugin-manager.jpg) diff --git a/docs/tr-TR/manual/quick-start/plugins/plugin-manager.jpg b/docs/tr-TR/manual/quick-start/plugins/plugin-manager.jpg new file mode 100644 index 000000000..ea5f937f6 Binary files /dev/null and b/docs/tr-TR/manual/quick-start/plugins/plugin-manager.jpg differ diff --git a/docs/tr-TR/manual/quick-start/the-first-app.md b/docs/tr-TR/manual/quick-start/the-first-app.md new file mode 100644 index 000000000..89e358da4 --- /dev/null +++ b/docs/tr-TR/manual/quick-start/the-first-app.md @@ -0,0 +1,91 @@ +# The First APP + +Let's build an order management system using NocoBase. + +## 1. Create data collections and fields + +In this order management system, we need to have the information of `Customers`,`Products`,`Orders` which are interrelated with each other. We need to create 4 data tables and their fields as follows: + +- Customers + - Name + - Address + - Gender + - Phone + - *Orders* (All orders purchased, data from `Orders` , each customer data contains multiple order data) +- Products + - Product name + - Description + - Images + - Price +- Orders + - Serial number + - Total + - Note + - Address + - *Customer* (The customer to which the order belongs to, which is a many-to-one relationship. Each order belongs to one customer, and one customer may have multiple orders) + - *Order List* (The items and quantities in this order are associated with `Order List`, which is a **one to many** relationship. Each order contains multiple order items, and each order items belongs to only one order) +- Order List + - Product (The product contained in this item whith is associated with `Products`, which is a **many-to-one** relationship. Each order item contains one product, and each product may belong to multiple order item) + - Quantity + +Where the fields in italics are relational fields, associated to other data tables. + +Next, click the "Collections & Fields" button to enter the Configuration screen and create the first Collection `Customers`. + +![create-collection.gif](./the-first-app/create-collection.gif) + +Then click on "Configure fields" to add a name field for `Customers`, which is a Single line text type. + +![create-field.gif](./the-first-app/create-field.gif) + +In the same way, add Address, Gender, and Phone for `Customers`, which are the Text, Radio group type, and Phone type respectively. + +![fields-list.jpg](./the-first-app/fields-list.jpg) + +In the same way, create Collections `Products`, `Orders`, `Order List` and their fields. + +![collection-list.jpg](./the-first-app/collection-list.jpg) + +In this case, for relational fields, if you are not familiar with the concepts of one-to-many, many-to-many, etc., you can directly use the Link to type to create associations between collections. If you are familiar with these concepts, please use the correct types of One to many, Many to one, etc. to establish the association between collections. For example, in this example, we associate `Orders` with `Order list`Order list with the relationship One to many. + +![collection-list.jpg](./the-first-app/order-list-relation.jpg) + + +In the graphical interface, you can visualize the relationship between the various collections. (Note: Graph-collection plugin is not yet open source) +![graph-collection.jpg](./the-first-app/graph-collection.jpg) + +Once the data collections and fields are created, we start making the interface. + +## 2. Configure menus and pages + +We need three pages for customers, orders, and products to display and manage our data. + +Click the UI Editor button to enter the interface configuration mode. In this mode, we can add menu items, add pages, and arrange blocks within the pages. + +![1.editor.gif](./the-first-app/1.editor.gif) + +Click Add menu item, add menu groups "Customers" and "Orders & Products", then add submenu pages "All Orders" and "Products". + +![1.menu.gif](./the-first-app/1.menu.gif) + +After adding menus and pages, we can add and configure blocks within the pages. + +## 3. Adding and Configuring Blocks + +NocoBase currently supports table, kanban, calendar, form, items, and other types of blocks that present data from a data collection and allow manipulation of the data. Obviously, customers, orders, and products are suitable for displaying and manipulating in a table block. + +We add a table block to the "All Orders" page, select Collection `Orders` as the data source, and configure the columns to be displayed for this table block. + +![1.block.gif](./the-first-app/1.block.gif) + +Configure actions for this table block, including filter, add, delete, view, and edit. + +![1.action.gif](./the-first-app/1.action.gif) + +Configure form and item blocks for add, edit, view actions. + +![1.action-block.gif](./the-first-app/1.action-block.gif) + +Then, lay out the form blocks on the Products and Customers pages with the same method. When you are done, exit the UI Editor mode and enter the usage mode, and a simple order management system is completed. + +![demo-finished.jpg](./the-first-app/demo-finished.jpg) diff --git a/docs/tr-TR/manual/quick-start/the-first-app/1.action-block.gif b/docs/tr-TR/manual/quick-start/the-first-app/1.action-block.gif new file mode 100644 index 000000000..ca6fbe81f Binary files /dev/null and b/docs/tr-TR/manual/quick-start/the-first-app/1.action-block.gif differ diff --git a/docs/tr-TR/manual/quick-start/the-first-app/1.action.gif b/docs/tr-TR/manual/quick-start/the-first-app/1.action.gif new file mode 100644 index 000000000..b83c948b2 Binary files /dev/null and b/docs/tr-TR/manual/quick-start/the-first-app/1.action.gif differ diff --git a/docs/tr-TR/manual/quick-start/the-first-app/1.block.gif b/docs/tr-TR/manual/quick-start/the-first-app/1.block.gif new file mode 100644 index 000000000..7f97496a2 Binary files /dev/null and b/docs/tr-TR/manual/quick-start/the-first-app/1.block.gif differ diff --git a/docs/tr-TR/manual/quick-start/the-first-app/1.editor.gif b/docs/tr-TR/manual/quick-start/the-first-app/1.editor.gif new file mode 100644 index 000000000..b69e3f0bd Binary files /dev/null and b/docs/tr-TR/manual/quick-start/the-first-app/1.editor.gif differ diff --git a/docs/tr-TR/manual/quick-start/the-first-app/1.menu.gif b/docs/tr-TR/manual/quick-start/the-first-app/1.menu.gif new file mode 100644 index 000000000..95f231201 Binary files /dev/null and b/docs/tr-TR/manual/quick-start/the-first-app/1.menu.gif differ diff --git a/docs/tr-TR/manual/quick-start/the-first-app/collection-list.jpg b/docs/tr-TR/manual/quick-start/the-first-app/collection-list.jpg new file mode 100644 index 000000000..c22230c9c Binary files /dev/null and b/docs/tr-TR/manual/quick-start/the-first-app/collection-list.jpg differ diff --git a/docs/tr-TR/manual/quick-start/the-first-app/create-collection.gif b/docs/tr-TR/manual/quick-start/the-first-app/create-collection.gif new file mode 100644 index 000000000..f472c7e6c Binary files /dev/null and b/docs/tr-TR/manual/quick-start/the-first-app/create-collection.gif differ diff --git a/docs/tr-TR/manual/quick-start/the-first-app/create-field.gif b/docs/tr-TR/manual/quick-start/the-first-app/create-field.gif new file mode 100644 index 000000000..9dc0183bb Binary files /dev/null and b/docs/tr-TR/manual/quick-start/the-first-app/create-field.gif differ diff --git a/docs/tr-TR/manual/quick-start/the-first-app/demo-finished.jpg b/docs/tr-TR/manual/quick-start/the-first-app/demo-finished.jpg new file mode 100644 index 000000000..6d9aac37a Binary files /dev/null and b/docs/tr-TR/manual/quick-start/the-first-app/demo-finished.jpg differ diff --git a/docs/tr-TR/manual/quick-start/the-first-app/fields-list.jpg b/docs/tr-TR/manual/quick-start/the-first-app/fields-list.jpg new file mode 100644 index 000000000..da9b564b4 Binary files /dev/null and b/docs/tr-TR/manual/quick-start/the-first-app/fields-list.jpg differ diff --git a/docs/tr-TR/manual/quick-start/the-first-app/graph-collection.jpg b/docs/tr-TR/manual/quick-start/the-first-app/graph-collection.jpg new file mode 100644 index 000000000..bd0ee4644 Binary files /dev/null and b/docs/tr-TR/manual/quick-start/the-first-app/graph-collection.jpg differ diff --git a/docs/tr-TR/manual/quick-start/the-first-app/order-list-relation.jpg b/docs/tr-TR/manual/quick-start/the-first-app/order-list-relation.jpg new file mode 100644 index 000000000..0ca27e1b3 Binary files /dev/null and b/docs/tr-TR/manual/quick-start/the-first-app/order-list-relation.jpg differ diff --git a/docs/tr-TR/manual/quick-start/ui-editor-mode.md b/docs/tr-TR/manual/quick-start/ui-editor-mode.md new file mode 100644 index 000000000..8ca8aeb36 --- /dev/null +++ b/docs/tr-TR/manual/quick-start/ui-editor-mode.md @@ -0,0 +1,51 @@ +# UI Editor Mode + +NocoBase uses a WYSIWYG approach to configure the interface. Click the `UI Editor` button in the upper right corner to switch between configuration mode and usage mode. Once you enter the configuration mode, many orange configuration portals will appear everywhere on the interface. + +![ui-editor.gif](./ui-editor-mode/ui-editor.gif) + +Usually, the configuration entry appears in the upper right corner of the element. + +## Menu item configuration + +Move the mouse over the menu item, in the upper right corner you can see the drag and drop sort button, configuration item button, you can edit, move, insert, delete. + +![menu-config.jpg](./ui-editor-mode/menu-config.jpg) + +## Block configuration + +Move the mouse over a block and you will see drag-and-drop sort button, add block button and configure item button in the upper right corner. + +![block-config.jpg](./ui-editor-mode/block-config.jpg) + +Different blocks also have their own unique configuration items. For example, in the table block, you can see the configuration items of the table header in the upper right corner by hovering over the table header; you can also see the configuration items of the table columns in the far right side of the table header. + +![block-config-2.jpg](./ui-editor-mode/block-config-2.jpg) + +![block-config-3.jpg](./ui-editor-mode/block-config-3.jpg) + +## Action configuration + +In the block you can see the configuration entries for the actions, which appear in different places in different blocks. + +For example, in the table block, the actions for the table data can be seen in the upper right: + +![action-config-1.jpg](./ui-editor-mode/action-config-1.jpg) + +In the table header of the actions column you can see the action for the single row of data: ! + +![action-config-2.jpg](./ui-editor-mode/action-config-2.jpg) + +You can see the actions for the details in the upper right corner of the details block: + +![action-config-3.jpg](./ui-editor-mode/action-config-3.jpg) + +The actions for the form can be seen at the bottom of the form block: + +![action-config-4.jpg](./ui-editor-mode/action-config-4.jpg) + +## Tab configuration + +Multiple tabs can be added in a popup or drawer to host different blocks. + +![tab-config.jpg](./ui-editor-mode/tab-config.jpg) \ No newline at end of file diff --git a/docs/tr-TR/manual/quick-start/ui-editor-mode/action-config-1.jpg b/docs/tr-TR/manual/quick-start/ui-editor-mode/action-config-1.jpg new file mode 100644 index 000000000..341deec28 Binary files /dev/null and b/docs/tr-TR/manual/quick-start/ui-editor-mode/action-config-1.jpg differ diff --git a/docs/tr-TR/manual/quick-start/ui-editor-mode/action-config-2.jpg b/docs/tr-TR/manual/quick-start/ui-editor-mode/action-config-2.jpg new file mode 100644 index 000000000..6d89ed582 Binary files /dev/null and b/docs/tr-TR/manual/quick-start/ui-editor-mode/action-config-2.jpg differ diff --git a/docs/tr-TR/manual/quick-start/ui-editor-mode/action-config-3.jpg b/docs/tr-TR/manual/quick-start/ui-editor-mode/action-config-3.jpg new file mode 100644 index 000000000..eb8b96c3c Binary files /dev/null and b/docs/tr-TR/manual/quick-start/ui-editor-mode/action-config-3.jpg differ diff --git a/docs/tr-TR/manual/quick-start/ui-editor-mode/action-config-4.jpg b/docs/tr-TR/manual/quick-start/ui-editor-mode/action-config-4.jpg new file mode 100644 index 000000000..eccb745e2 Binary files /dev/null and b/docs/tr-TR/manual/quick-start/ui-editor-mode/action-config-4.jpg differ diff --git a/docs/tr-TR/manual/quick-start/ui-editor-mode/block-config-2.jpg b/docs/tr-TR/manual/quick-start/ui-editor-mode/block-config-2.jpg new file mode 100644 index 000000000..3fd7a2390 Binary files /dev/null and b/docs/tr-TR/manual/quick-start/ui-editor-mode/block-config-2.jpg differ diff --git a/docs/tr-TR/manual/quick-start/ui-editor-mode/block-config-3.jpg b/docs/tr-TR/manual/quick-start/ui-editor-mode/block-config-3.jpg new file mode 100644 index 000000000..114d7fd42 Binary files /dev/null and b/docs/tr-TR/manual/quick-start/ui-editor-mode/block-config-3.jpg differ diff --git a/docs/tr-TR/manual/quick-start/ui-editor-mode/block-config.jpg b/docs/tr-TR/manual/quick-start/ui-editor-mode/block-config.jpg new file mode 100644 index 000000000..d39a7b192 Binary files /dev/null and b/docs/tr-TR/manual/quick-start/ui-editor-mode/block-config.jpg differ diff --git a/docs/tr-TR/manual/quick-start/ui-editor-mode/menu-config.jpg b/docs/tr-TR/manual/quick-start/ui-editor-mode/menu-config.jpg new file mode 100644 index 000000000..b46fc2f99 Binary files /dev/null and b/docs/tr-TR/manual/quick-start/ui-editor-mode/menu-config.jpg differ diff --git a/docs/tr-TR/manual/quick-start/ui-editor-mode/tab-config.jpg b/docs/tr-TR/manual/quick-start/ui-editor-mode/tab-config.jpg new file mode 100644 index 000000000..f2653f66a Binary files /dev/null and b/docs/tr-TR/manual/quick-start/ui-editor-mode/tab-config.jpg differ diff --git a/docs/tr-TR/manual/quick-start/ui-editor-mode/ui-editor.gif b/docs/tr-TR/manual/quick-start/ui-editor-mode/ui-editor.gif new file mode 100644 index 000000000..eefbee917 Binary files /dev/null and b/docs/tr-TR/manual/quick-start/ui-editor-mode/ui-editor.gif differ diff --git a/docs/tr-TR/welcome/community/contributing.md b/docs/tr-TR/welcome/community/contributing.md new file mode 100644 index 000000000..beb193751 --- /dev/null +++ b/docs/tr-TR/welcome/community/contributing.md @@ -0,0 +1,50 @@ +# Katkı + +- Kaynak kodunu kendi deponuza alın(fork) +- Kaynak kodunu değiştir +- Çekme isteği(pull request) gönderin +- CLA'yı imzalayın + +## İndir + +```bash +# Aşağıdaki git adresini kendi deponuzla değiştirin +git clone https://github.com/nocobase/nocobase.git +cd nocobase +yarn install +``` + +## Geliştirme ve Test + +```bash +# Uygulamayı kurun ve başlatın +yarn dev +# Tüm testleri çalıştır +yarn test +# Klasördeki tüm test dosyalarını çalıştırın +yarn test +# Tek bir test dosyası çalıştırın +yarn test +``` + +## Belge önizlemesi + +```bash +# Belgeleri başlat +yarn doc --lang=zh-CN +yarn doc --lang=en-US +yarn doc --lang=tr-TR +``` + +Belgeler docs dizinindedir ve Markdown sözdizimini takip eder + +```bash +|- /docs/ + |- en-US + |- tr-TR + |- zh-CN +``` + +## Diğerleri + +Daha fazla CLI talimatı için lütfen [NocoBase CLI bölümüne bakın](./development/nocobase-cli.md) diff --git a/docs/tr-TR/welcome/community/faq.md b/docs/tr-TR/welcome/community/faq.md new file mode 100644 index 000000000..2f55fb765 --- /dev/null +++ b/docs/tr-TR/welcome/community/faq.md @@ -0,0 +1 @@ +# Sıkça Sorulan Sorular diff --git a/docs/tr-TR/welcome/community/thanks.md b/docs/tr-TR/welcome/community/thanks.md new file mode 100644 index 000000000..19d09feee --- /dev/null +++ b/docs/tr-TR/welcome/community/thanks.md @@ -0,0 +1,12 @@ +# Teşekkür + +NocoBase, aşağıda isimleri geçen ve içten şükranlarımızı ifade ettiğimiz mükemmel ve kanıtlanmış açık kaynaklı ürünleri kullanır. + +- [Ant Design](https://ant.design/) +- [Dnd Kit](https://dndkit.com/) +- [Formily](https://github.com/alibaba/formily) +- [I18next](https://www.i18next.com/) +- [Koa](https://koajs.com/) +- [React](https://reactjs.org/) +- [Sequelize](https://sequelize.org/) +- [UmiJS](https://umijs.org/) diff --git a/docs/tr-TR/welcome/community/translations.md b/docs/tr-TR/welcome/community/translations.md new file mode 100644 index 000000000..fb065ebd6 --- /dev/null +++ b/docs/tr-TR/welcome/community/translations.md @@ -0,0 +1,159 @@ +# Çeviriler + +NocoBase'in varsayılan dili İngilizce'dir, şu anda İngilizce, Basitleştirilmiş Çince, Japonca, Rusça, Türkçe desteklenmektedir. NocoBase'in kendi dilinize tercüme etmesine yardımcı olabilirsiniz. + +NocoBase dil dosyaları aşağıdaki konumlarda bulunur. + +```shell +packages/core/**/src/locale +packages/plugins/**/src/locale +``` + +Diğer şeylerin yanı sıra, NocoBase çekirdeğinin çevirisi esas olarak burada bulunur + +https://github.com/nocobase/nocobase/tree/main/packages/core/client/src/locale + +Lütfen en_US.ts dosyasını kopyalayın, eklemek istediğiniz dilin adıyla adlandırın ve ardından içindeki dizeleri çevirin. Çeviri tamamlandıktan sonra, lütfen çekme isteği ile NocoBase'e gönderin, dil listesine ekleyeceğiz. Ardından, kullanıcıların seçmesi için hangi dilleri görüntülemek istediğinizi yapılandırabileceğiniz sistem yapılandırmasında yeni dilleri göreceksiniz. + + + +Aşağıdaki tabloda Dil Kültürü Adı, Yerel Dosya Adı, Görünen Ad listelenmektedir. + +| Dil Kültür Adı | Yerel Dosya Adı | Ekran adı | +| :-------------------- | :--------------- | :---------------------------- | +| af-ZA | af_ZA.ts | Afrikaans - South Africa | +| sq-AL | sq_AL.ts | Albanian - Albania | +| ar-DZ | ar_DZ.ts | Arabic - Algeria | +| ar-BH | ar_BH.ts | Arabic - Bahrain | +| ar-EG | ar_EG.ts | Arabic - Egypt | +| ar-IQ | ar_IQ.ts | Arabic - Iraq | +| ar-JO | ar_JO.ts | Arabic - Jordan | +| ar-KW | ar_KW.ts | Arabic - Kuwait | +| ar-LB | ar_LB.ts | Arabic - Lebanon | +| ar-LY | ar_LY.ts | Arabic - Libya | +| ar-MA | ar_MA.ts | Arabic - Morocco | +| ar-OM | ar_OM.ts | Arabic - Oman | +| ar-QA | ar_QA.ts | Arabic - Qatar | +| ar-SA | ar_SA.ts | Arabic - Saudi Arabia | +| ar-SY | ar_SY.ts | Arabic - Syria | +| ar-TN | ar_TN.ts | Arabic - Tunisia | +| ar-AE | ar_AE.ts | Arabic - United Arab Emirates | +| ar-YE | ar_YE.ts | Arabic - Yemen | +| hy-AM | hy_AM.ts | Armenian - Armenia | +| Cy-az-AZ | Cy_az_AZ.ts | Azeri (Cyrillic) - Azerbaijan | +| Lt-az-AZ | Lt_az_AZ.ts | Azeri (Latin) - Azerbaijan | +| eu-ES | eu_ES.ts | Basque - Basque | +| be-BY | be_BY.ts | Belarusian - Belarus | +| bg-BG | bg_BG.ts | Bulgarian - Bulgaria | +| ca-ES | ca_ES.ts | Catalan - Catalan | +| zh-CN | zh_CN.ts | Chinese - China | +| zh-HK | zh_HK.ts | Chinese - Hong Kong SAR | +| zh-MO | zh_MO.ts | Chinese - Macau SAR | +| zh-SG | zh_SG.ts | Chinese - Singapore | +| zh-TW | zh_TW.ts | Chinese - Taiwan | +| zh-CHS | zh_CHS.ts | Chinese (Simplified) | +| zh-CHT | zh_CHT.ts | Chinese (Traditional) | +| hr-HR | hr_HR.ts | Croatian - Croatia | +| cs-CZ | cs_CZ.ts | Czech - Czech Republic | +| da-DK | da_DK.ts | Danish - Denmark | +| div-MV | div_MV.ts | Dhivehi - Maldives | +| nl-BE | nl_BE.ts | Dutch - Belgium | +| nl-NL | nl_NL.ts | Dutch - The Netherlands | +| en-AU | en_AU.ts | English - Australia | +| en-BZ | en_BZ.ts | English - Belize | +| en-CA | en_CA.ts | English - Canada | +| en-CB | en_CB.ts | English - Caribbean | +| en-IE | en_IE.ts | English - Ireland | +| en-JM | en_JM.ts | English - Jamaica | +| en-NZ | en_NZ.ts | English - New Zealand | +| en-PH | en_PH.ts | English - Philippines | +| en-ZA | en_ZA.ts | English - South Africa | +| en-TT | en_TT.ts | English - Trinidad and Tobago | +| en-GB | en_GB.ts | English - United Kingdom | +| en-US | en_US.ts | English - United States | +| en-ZW | en_ZW.ts | English - Zimbabwe | +| et-EE | et_EE.ts | Estonian - Estonia | +| fo-FO | fo_FO.ts | Faroese - Faroe Islands | +| fa-IR | fa_IR.ts | Farsi - Iran | +| fi-FI | fi_FI.ts | Finnish - Finland | +| fr-BE | fr_BE.ts | French - Belgium | +| fr-CA | fr_CA.ts | French - Canada | +| fr-FR | fr_FR.ts | French - France | +| fr-LU | fr_LU.ts | French - Luxembourg | +| fr-MC | fr_MC.ts | French - Monaco | +| fr-CH | fr_CH.ts | French - Switzerland | +| gl-ES | gl_ES.ts | Galician - Galician | +| ka-GE | ka_GE.ts | Georgian - Georgia | +| de-AT | de_AT.ts | German - Austria | +| de-DE | de_DE.ts | German - Germany | +| de-LI | de_LI.ts | German - Liechtenstein | +| de-LU | de_LU.ts | German - Luxembourg | +| de-CH | de_CH.ts | German - Switzerland | +| el-GR | el_GR.ts | Greek - Greece | +| gu-IN | gu_IN.ts | Gujarati - India | +| he-IL | he_IL.ts | Hebrew - Israel | +| hi-IN | hi_IN.ts | Hindi - India | +| hu-HU | hu_HU.ts | Hungarian - Hungary | +| is-IS | is_IS.ts | Icelandic - Iceland | +| id-ID | id_ID.ts | Indonesian - Indonesia | +| it-IT | it_IT.ts | Italian - Italy | +| it-CH | it_CH.ts | Italian - Switzerland | +| ja-JP | ja_JP.ts | Japanese - Japan | +| kn-IN | kn_IN.ts | Kannada - India | +| kk-KZ | kk_KZ.ts | Kazakh - Kazakhstan | +| kok-IN | kok_IN.ts | Konkani - India | +| ko-KR | ko_KR.ts | Korean - Korea | +| ky-KZ | ky_KZ.ts | Kyrgyz - Kazakhstan | +| lv-LV | lv_LV.ts | Latvian - Latvia | +| lt-LT | lt_LT.ts | Lithuanian - Lithuania | +| mk-MK | mk_MK.ts | Macedonian (FYROM) | +| ms-BN | ms_BN.ts | Malay - Brunei | +| ms-MY | ms_MY.ts | Malay - Malaysia | +| mr-IN | mr_IN.ts | Marathi - India | +| mn-MN | mn_MN.ts | Mongolian - Mongolia | +| nb-NO | nb_NO.ts | Norwegian (BokmÃ¥l) - Norway | +| nn-NO | nn_NO.ts | Norwegian (Nynorsk) - Norway | +| pl-PL | pl_PL.ts | Polish - Poland | +| pt-BR | pt_BR.ts | Portuguese - Brazil | +| pt-PT | pt_PT.ts | Portuguese - Portugal | +| pa-IN | pa_IN.ts | Punjabi - India | +| ro-RO | ro_RO.ts | Romanian - Romania | +| ru-RU | ru_RU.ts | Russian - Russia | +| sa-IN | sa_IN.ts | Sanskrit - India | +| Cy-sr-SP | Cy_sr_SP.ts | Serbian (Cyrillic) - Serbia | +| Lt-sr-SP | Lt_sr_SP.ts | Serbian (Latin) - Serbia | +| sk-SK | sk_SK.ts | Slovak - Slovakia | +| sl-SI | sl_SI.ts | Slovenian - Slovenia | +| es-AR | es_AR.ts | Spanish - Argentina | +| es-BO | es_BO.ts | Spanish - Bolivia | +| es-CL | es_CL.ts | Spanish - Chile | +| es-CO | es_CO.ts | Spanish - Colombia | +| es-CR | es_CR.ts | Spanish - Costa Rica | +| es-DO | es_DO.ts | Spanish - Dominican Republic | +| es-EC | es_EC.ts | Spanish - Ecuador | +| es-SV | es_SV.ts | Spanish - El Salvador | +| es-GT | es_GT.ts | Spanish - Guatemala | +| es-HN | es_HN.ts | Spanish - Honduras | +| es-MX | es_MX.ts | Spanish - Mexico | +| es-NI | es_NI.ts | Spanish - Nicaragua | +| es-PA | es_PA.ts | Spanish - Panama | +| es-PY | es_PY.ts | Spanish - Paraguay | +| es-PE | es_PE.ts | Spanish - Peru | +| es-PR | es_PR.ts | Spanish - Puerto Rico | +| es-ES | es_ES.ts | Spanish - Spain | +| es-UY | es_UY.ts | Spanish - Uruguay | +| es-VE | es_VE.ts | Spanish - Venezuela | +| sw-KE | sw_KE.ts | Swahili - Kenya | +| sv-FI | sv_FI.ts | Swedish - Finland | +| sv-SE | sv_SE.ts | Swedish - Sweden | +| syr-SY | syr_SY.ts | Syriac - Syria | +| ta-IN | ta_IN.ts | Tamil - India | +| tt-RU | tt_RU.ts | Tatar - Russia | +| te-IN | te_IN.ts | Telugu - India | +| th-TH | th_TH.ts | Thai - Thailand | +| tr-TR | tr_TR.ts | Turkish - Turkey | +| uk-UA | uk_UA.ts | Ukrainian - Ukraine | +| ur-PK | ur_PK.ts | Urdu - Pakistan | +| Cy-uz-UZ | Cy_uz_UZ.ts | Uzbek (Cyrillic) - Uzbekistan | +| Lt-uz-UZ | Lt_uz_UZ.ts | Uzbek (Latin) - Uzbekistan | +| vi-VN | vi_VN.ts | Vietnamese - Vietnam | diff --git a/docs/tr-TR/welcome/community/translations/enabled-languages.jpg b/docs/tr-TR/welcome/community/translations/enabled-languages.jpg new file mode 100644 index 000000000..ad3767e35 Binary files /dev/null and b/docs/tr-TR/welcome/community/translations/enabled-languages.jpg differ diff --git a/docs/tr-TR/welcome/community/translations/language-settings-1.jpg b/docs/tr-TR/welcome/community/translations/language-settings-1.jpg new file mode 100644 index 000000000..866267a0f Binary files /dev/null and b/docs/tr-TR/welcome/community/translations/language-settings-1.jpg differ diff --git a/docs/tr-TR/welcome/community/translations/language-settings-2.jpg b/docs/tr-TR/welcome/community/translations/language-settings-2.jpg new file mode 100644 index 000000000..37de523f0 Binary files /dev/null and b/docs/tr-TR/welcome/community/translations/language-settings-2.jpg differ diff --git a/docs/tr-TR/welcome/getting-started/deployment.md b/docs/tr-TR/welcome/getting-started/deployment.md new file mode 100644 index 000000000..ed3c9a4c8 --- /dev/null +++ b/docs/tr-TR/welcome/getting-started/deployment.md @@ -0,0 +1,2 @@ +# Deployment + diff --git a/docs/tr-TR/welcome/getting-started/installation/create-nocobase-app.md b/docs/tr-TR/welcome/getting-started/installation/create-nocobase-app.md new file mode 100644 index 000000000..0b7548362 --- /dev/null +++ b/docs/tr-TR/welcome/getting-started/installation/create-nocobase-app.md @@ -0,0 +1,86 @@ +# `create-nocobase-app` + +## 0. Önkoşullar + +Aşağıdaki gereksinimlere sahip olduğunuza emin olun: + +- Node.js 14+, Yarn 1.22.x kurulu olmalı +- SQLite 3.x, MySQL 8.x, PostgreSQL 10.x veritabanlarından birisinin kurulup yapılandırıldığı ve çalışır durumda olduğuna emin olun + +Lütfen Node.js 14.x veya üstünün kurulu olduğundan emin olun. En son LTS sürümünü resmi web sitesinden indirebilir ve yükleyebilirsiniz. Node.js ile uzun süre çalışmayı planlıyorsanız, Node.js sürümlerini yönetmek için nvm (veya Win sistemleri için nvm-windows) kullanmanız önerilir. + +```bash +$ node -v + +v16.13.2 +``` + +yarn paket yöneticisini kurun + +```bash +$ npm install --global yarn +$ yarn -v + +1.22.10 +``` + +## 1. NocoBase projesi oluştur + +```bash +# SQLite +yarn create nocobase-app my-nocobase-app -d sqlite +# MySQL +yarn create nocobase-app my-nocobase-app -d mysql \ + -e DB_HOST=localhost \ + -e DB_PORT=3306 \ + -e DB_DATABASE=nocobase \ + -e DB_USER=nocobase \ + -e DB_PASSWORD=nocobase +# PostgreSQL +yarn create nocobase-app my-nocobase-app -d postgres \ + -e DB_HOST=localhost \ + -e DB_PORT=5432 \ + -e DB_DATABASE=nocobase \ + -e DB_USER=nocobase \ + -e DB_PASSWORD=nocobase +``` + +## 2. Proje klasörüne girin + +```bash +cd my-nocobase-app +``` + +## 3. Bağımlılıkları kurun + +📢 Bu adım, ağ ortamı, sistem yapılandırması ve diğer faktörler nedeniyle on dakikadan fazla sürebilir. + +```bash +yarn install +``` + +## 4. NocoBase Kurun + +```bash +yarn nocobase install --lang=tr-TR +``` + +## 5. NocoBase Başlatın + +Geliştirme + +```bash +yarn dev +``` + +Ürün yayınlama + +```bash +yarn start # windows platformunda çalışmamaktadır. +``` + +Not: Üretim için, kod değiştirilmişse, "yarn build"i çalıştırmanız ve NocoBase'i yeniden başlatmanız gerekir. + +## 6. NocoBase Giriş yapıp kullanmaya başlayın + +Bir web tarayıcısında [http://localhost:13000](http://localhost:13000) adresini açın. İlk hesap ve parola `admin@nocobase.com` ve `admin123` şeklindedir. Giriş yaptıktan sonra bu bilgileri güvenliğiniz için mutlaka değiştirin. diff --git a/docs/tr-TR/welcome/getting-started/installation/docker-compose.md b/docs/tr-TR/welcome/getting-started/installation/docker-compose.md new file mode 100644 index 000000000..4022e2bfb --- /dev/null +++ b/docs/tr-TR/welcome/getting-started/installation/docker-compose.md @@ -0,0 +1,140 @@ +# Docker (👍 Tavsiye edilen) + +## 0. Önkoşullar + +⚡⚡ [Docker](https://docs.docker.com/get-docker/) sisteminin kurulu olduğuna emin olun. + +## 1. NocoBase İndirin + +Git ile indirin (veya Zip'i İndirin ve onu nocobase dizinine çıkarın) + +```bash +git clone https://github.com/nocobase/nocobase.git nocobase +``` + +## 2. Veritabanı seçin (Bir tane seçin) + +Terminalde bulunduğunuz dizini indirdiğiniz klasöre göre değiştirip içerisine girin. + +```bash +# MacOS, Linux... +cd /your/path/nocobase +# Windows +cd C:\your\path\nocobase +``` + +Farklı veritabanlarının docker konfigürasyonu birbirinden farklıdır, lütfen kullanmak istediğiniz veritabanın olduğu dizine geçin. + +### SQLite + +```bash +cd docker/app-sqlite +``` + +### MySQL + +```bash +cd docker/app-mysql +``` + +### PostgreSQL + +```bash +cd docker/app-postgres +``` + +## 3. docker-compose.yml dosyasını yapılandırın (opsiyonel) + + + +Geliştirici olmayanlar bu adımı atlayabilir. Standart gelen ayarlar ile sisteminiz çalışacaktır. Geliştirmeyi biliyorsanız, `docker-compose.yml`yi nasıl yapılandıracağınız hakkında daha fazla bilgi edinebilirsiniz. + + + +Dizin yapısı (docker ile ilgili) + +```bash +├── nocobase + ├── docker + ├── app-sqlite + ├── storage + ├── docker-compose.yml + ├── app-mysql + ├── storage + ├── docker-compose.yml + ├── app-postgres + ├── storage + ├── docker-compose.yml +``` + +`docker-compose.yml` için yapılandırma notları: + +SQLite sadece uygulama servisine sahiptir, PostgreSQL ve MySQL'in ilgili postgres veya mysql servisi olacaktır, örnek veritabanı servisini kullanabilir veya kendiniz yapılandırabilirsiniz. + +```yml +services: + app: + postgres: + mysql: +``` + +Uygulama portu adresi `http://your-ip:13000/` + +```yml +services: + app: + ports: + - "13000:80" +``` + +NocoBase sürümü ([en son sürüm için burayı tıklayın](https://hub.docker.com/r/nocobase/nocobase/tags)). Yükseltme yaparken, en son sürüme geçmeniz gerekir. + +```yml +services: + app: + image: nocobase/nocobase:0.7.0-alpha.78 +``` + +Ortam Değişkenleri + +```yml +services: + app: + image: nocobase/nocobase:0.7.0-alpha.78 + environment: + - DB_DIALECT=postgres + - DB_HOST=postgres + - DB_DATABASE=nocobase + - DB_USER=nocobase + - DB_PASSWORD=nocobase + - LOCAL_STORAGE_BASE_URL=http://localhost:13000/storage/uploads +``` + +- `DB_*` ilgili veritabanıdır, örneğin varsayılan veritabanı hizmeti değilse, lütfen fiili duruma göre değiştirin. +- 'LOCAL_STORAGE_BASE_URL', yerel depolama için temel URL'dir, yerel bir kurulum değilse, onu ilgili ip veya etki alanı adıyla değiştirmeniz gerekir. + +## 4. NocoBase Kurulum ve Başlatma + +Birkaç dakika sürebilir + +```bash +# arka planda çalıştır +$ docker-compose up -d +# uygulama günlüklerini görüntüle +$ docker-compose logs app + +app-sqlite-app-1 | nginx started +app-sqlite-app-1 | yarn run v1.22.15 +app-sqlite-app-1 | $ cross-env DOTENV_CONFIG_PATH=.env node -r dotenv/config packages/app/server/lib/index.js install -s +app-sqlite-app-1 | Done in 2.72s. +app-sqlite-app-1 | yarn run v1.22.15 +app-sqlite-app-1 | $ pm2-runtime start --node-args="-r dotenv/config" packages/app/server/lib/index.js -- start +app-sqlite-app-1 | 2022-04-28T15:45:38: PM2 log: Launching in no daemon mode +app-sqlite-app-1 | 2022-04-28T15:45:38: PM2 log: App [index:0] starting in -fork mode- +app-sqlite-app-1 | 2022-04-28T15:45:38: PM2 log: App [index:0] online +app-sqlite-app-1 | 🚀 NocoBase server running at: http://localhost:13000/ +``` + +## 4. NocoBase Giriş yapıp kullanmaya başlayın + +Bir web tarayıcısında [http://localhost:13000](http://localhost:13000) adresini açın. İlk hesap ve parola `admin@nocobase.com` ve `admin123` şeklindedir. Giriş yaptıktan sonra bu bilgileri güvenliğiniz için mutlaka değiştirin. diff --git a/docs/tr-TR/welcome/getting-started/installation/git-clone.md b/docs/tr-TR/welcome/getting-started/installation/git-clone.md new file mode 100644 index 000000000..93d1735b8 --- /dev/null +++ b/docs/tr-TR/welcome/getting-started/installation/git-clone.md @@ -0,0 +1,64 @@ +# Git kaynak kodlarından derleme + +## 0. Önkoşullar + +Aşağıdaki gereksinimlere sahip olduğunuza emin olun: + +- Node.js 14+, Yarn 1.22.x kurulu olmalı +- SQLite 3.x, MySQL 8.x, PostgreSQL 10.x veritabanlarından birisinin kurulup yapılandırıldığı ve çalışır durumda olduğuna emin olun + +## 1. Git ile indirin + +```bash +git clone https://github.com/nocobase/nocobase.git my-nocobase-app +``` + +## 2. Proje klasörüne girin + +```bash +cd my-nocobase-app +``` + +## 3. Bağımlılıkları kurun + +```bash +yarn install +``` + +## 4. Ortam değişkenlerini ayarla + +NocoBase tarafından gerekli olan ortam değişkenleri kök `.env` dosyasında saklanır, ortam değişkenlerini gerçek duruma göre değiştirin, eğer onları nasıl değiştireceğinizi bilmiyorsanız, [ortam değişkenleri açıklaması için buraya tıklayın](/api/env) veya varsayılan olarak bırakabilirsiniz. + +```bash +# sqlite veritabanını kullan +DB_DIALECT=sqlite +# sqlite dosya yolu +DB_STORAGE=storage/db/nocobase.sqlite +``` + +## 5. NocoBase Kurun + +```bash +yarn nocobase install --lang=tr-TR +``` + +## 6. NocoBase Başlatın + +Geliştirme + +```bash +yarn dev +``` + +Ürün yayınlama + +```bash +# Derleme +yarn build +# Başlatma +yarn start # windows platformunda çalışmamaktadır. +``` + +## 7. NocoBase Giriş yapıp kullanmaya başlayın + +Bir web tarayıcısında [http://localhost:13000](http://localhost:13000) adresini açın. İlk hesap ve parola `admin@nocobase.com` ve `admin123` şeklindedir. Giriş yaptıktan sonra bu bilgileri güvenliğiniz için mutlaka değiştirin. diff --git a/docs/tr-TR/welcome/getting-started/installation/index.md b/docs/tr-TR/welcome/getting-started/installation/index.md new file mode 100644 index 000000000..b3dad8e34 --- /dev/null +++ b/docs/tr-TR/welcome/getting-started/installation/index.md @@ -0,0 +1,23 @@ +# Genel Bakış + +## Kurulum metodları + +NocoBase üç yükleme yöntemini destekler. + +- [Docker (tavsiye edilen)](./docker-compose.md) +- [create-nocobase-app](./create-nocobase-app.md) +- [Git kaynak kodu](./git-clone.md) + +## Nasıl seçilir + +**Docker (tavsiye edilen)**: + +Kodsuz senaryolar için uygundur, yazılacak kod yoktur. Yükseltme yaparken, sadece en son görüntüyü indirin ve yeniden başlatın. + +**create-nocobase-app**: + +Projenin iş kodu tamamen bağımsızdır ve düşük kod geliştirmeyi destekler. + +**Git kaynak kodlarından derleme**: + +Yayınlanmamış en son sürümü deneyimlemek veya katkıda bulunmak istiyorsanız, kaynak kodunda değişiklik yapmanız ve hata ayıklamanız gerekiyorsa, yüksek düzeyde geliştirme becerisi gerektiren bu kurulum yöntemini seçmeniz önerilir. Eğer kodlar güncellendiyse git pull ile son sürümü çekebilirsiniz. diff --git a/docs/tr-TR/welcome/getting-started/upgrading/create-nocobase-app.md b/docs/tr-TR/welcome/getting-started/upgrading/create-nocobase-app.md new file mode 100644 index 000000000..3b9caa109 --- /dev/null +++ b/docs/tr-TR/welcome/getting-started/upgrading/create-nocobase-app.md @@ -0,0 +1,75 @@ +# `create-nocobase-app` için güncelleme + +## Küçük sürüm yükseltmesi + +Sadece `nocobase upgrade` güncelleme komutunu çalıştırın + +```bash +# Uygulamanızın olduğu dizine geçin +cd my-nocobase-app +# Güncelleme komutunu çalıştırın +yarn nocobase upgrade +# Başlat +yarn dev +``` + +## Büyük yükseltme + +Küçük yükseltme işleminiz başarısız olursa da bu yükseltme yöntemini kullanabilirsiniz. + +### 1. Yeni bir NocoBase projesi oluşturun + +```bash +## SQLite +yarn create nocobase-app my-nocobase-app -d sqlite +# MySQL +yarn create nocobase-app my-nocobase-app -d mysql +# PostgreSQL +yarn create nocobase-app my-nocobase-app -d postgres +``` + +### 2. Proje klasörüne girin + +```bash +cd my-nocobase-app +``` + +### 3. Bağımlılıkları kurun + +📢 Bu adım ağ ortamı, sistem yapılandırması ve diğer faktörler nedeniyle on dakikadan fazla sürebilir. + +```bash +yarn install +``` + +### 4. .env yapılandırmasını değiştirin + +Değiştirmek için eski .env sürümüne bakın, veritabanı bilgilerinin doğru şekilde yapılandırılması gerekir. SQLite veritabanının da `. /storage/db/` dizini. + +### 5. Eski kod geçişi (gerekli değil) + +İş kodu, eklenti geliştirme öğreticisinin yeni sürümüne ve değişiklik için API referansına başvurur. + +### 6. Yükseltme komutunu çalıştırın + +Kod zaten en son sürüm olduğundan, yükseltme yaparken `--skip-code-update' kod güncellemesini atlamanız gerekir. + +```bash +yarn nocobase upgrade --skip-code-update +``` + +### 7. NocoBase Başlatın + +geliştirme ortamı + +```bash +yarn dev +``` + +Ürün yayınlama ortamı + +```bash +yarn start # windows platformunda çalışmamaktadır. +``` + +Not: Üretim ortamı için kod değiştirilmişse, ``yarn build`` komutunu çalıştırmanız ve NocoBase'i yeniden başlatmanız gerekir. \ No newline at end of file diff --git a/docs/tr-TR/welcome/getting-started/upgrading/docker-compose.md b/docs/tr-TR/welcome/getting-started/upgrading/docker-compose.md new file mode 100644 index 000000000..061bf6427 --- /dev/null +++ b/docs/tr-TR/welcome/getting-started/upgrading/docker-compose.md @@ -0,0 +1,60 @@ +# Docker için yükseltme + + + +Bu belgede açıklanan Docker kurulumu, [NocoBase GitHub deposunda](https://github.com/nocobase/nocobase/tree/main/docker) bulunan `docker-compose.yml` yapılandırma dosyasına dayanmaktadır.). + + + +## 1. Daha önce kurduğunuz dizine geçin + +Ayrıca duruma göre `docker-compose.yml` dosyasının bulunduğu dizine de geçebilirsiniz. + +```bash +# SQLite +cd nocobase/docker/app-sqlite +# MySQL +cd nocobase/docker/app-mysql +# PostgreSQL +cd nocobase/docker/app-postgres +``` + +## 2. İmaj sürüm numarasını güncelleyin + +`docker-compose.yml` dosyası, uygulama kapsayıcısının görüntüsünü en son sürümle değiştirin. + +```yml +services: + app: + image: nocobase/nocobase:0.8.0-alpha.1 +``` + +## 3. Eski imajları sil (gerekli değil) + +En son imajı kullanıyorsanız, önce ilgili konteyneri(docker imajı) durdurmanız ve silmeniz gerekir. + +```bash +# Konteyner ID bul +docker ps +# Konteyneri durdur +docker stop +# Konteyneri sil +docker rm +``` + +Eski imajı sil + +```bash +# İmajı bul +docker images +# İmajı sil +docker rmi +``` + +## 4. Konteyneri yeniden başlat + +```bash +docker-compose up -d app +# Uygulama sürecinin durumunu görüntüleyin +docker-compose logs app +``` diff --git a/docs/tr-TR/welcome/getting-started/upgrading/git-clone.md b/docs/tr-TR/welcome/getting-started/upgrading/git-clone.md new file mode 100644 index 000000000..8a7710ecf --- /dev/null +++ b/docs/tr-TR/welcome/getting-started/upgrading/git-clone.md @@ -0,0 +1,44 @@ +# Git kaynak kodundan güncelleme + +## 1. NocoBase Proje klasörüne girin + +```bash +cd my-nocobase-app +``` + +## 2. En son kodu çekin + +```bash +git pull +``` + +## 3. Bağımlılıkları güncelle + +```bash +yarn install +``` + +## 4. Güncelleme komutunu çalıştırın + +```bash +yarn nocobase upgrade +``` + +## 5. NocoBase Başlatın + +geliştirme ortamı + +```bash +yarn dev +``` + +Ürün yayınlama ortamı + +```bash +# Derle +yarn build + +# Başlat +yarn start # windows platformunda çalışmamaktadır. +``` + diff --git a/docs/tr-TR/welcome/getting-started/upgrading/index.md b/docs/tr-TR/welcome/getting-started/upgrading/index.md new file mode 100644 index 000000000..3b022ccb6 --- /dev/null +++ b/docs/tr-TR/welcome/getting-started/upgrading/index.md @@ -0,0 +1,7 @@ +# Genel Bakış + +NocoBase, yükseltmelerde küçük farklılıklarla üç tür yüklemeyi destekler. + +- [Docker için Güncelleme](./upgrading/docker-compose.md) +- [create-nocobase-app için Güncelleme](./upgrading/create-nocobase-app.md) +- [Git Kaynak koduyla Güncelleme](./upgrading/git-clone.md) diff --git a/docs/tr-TR/welcome/introduction/features.md b/docs/tr-TR/welcome/introduction/features.md new file mode 100644 index 000000000..698bd7aed --- /dev/null +++ b/docs/tr-TR/welcome/introduction/features.md @@ -0,0 +1,21 @@ +# NocoBase neden farklıdır? + +## Ayrı "veri yapısı" ve "kullanıcı arayüzü" + +Çoğu kodsuz(nocode) uygulamalar form, tablo veya süreç odaklıdır. bir tabloya yeni bir sütun eklemenin yeni bir alan eklemek anlamına geldiği Airtable gibi doğrudan kullanıcı arabiriminde veri yapıları oluşturur. Bu, kullanım kolaylığı avantajına sahiptir fakat daha karmaşık senaryoların ihtiyaçlarını karşılamak için sınırlı işlevsellik ve esneklik dezavantajına sahiptir. + +NocoBase, veri yapısını kullanıcı arayüzünden ayırma tasarım fikrini benimser ve her blokta farklı tür, stil, içerik ve eylemlerle veri koleksiyonları için istediğiniz sayıda blok (veri görünümü) oluşturmanıza olanak tanır. Bu yapısal durum kodsuz işlemin basitliğini ve aynı zamanda yerel geliştirme gibi esnekliği de hesaba katar. + +![2.collection-block.png](./features/2.collection-block.png) + +## 2. Ne görüyorsanız onu alırsınız + +NocoBase, karmaşık ve farklı iş sistemlerinin geliştirilmesine olanak tanır, ancak bu karmaşık ve özel operasyonların gerekli olduğu anlamına gelmez. Tek bir tıklama ile kullanım arayüzünde yapılandırma seçenekleri görüntülenebilir. Bu da sistem yapılandırma haklarına sahip yöneticilerin kullanıcı arayüzünü doğrudan WYSIWYG işlemleri ile yapılandırılabileceği anlamına gelir. + +![2.user-root.gif](./features/2.user-root.gif) + +## 3. Her şey bir eklenti + +NocoBase eklenti mimarisini benimser, tüm yeni özellikler eklentiler geliştirip yükleyerek uygulanabilir. Gelecekte, işlevselliği genişletmenin telefonunuza bir uygulama yüklemek kadar kolay olduğu bir eklenti pazarı oluşturacağız. + +![](https://www.nocobase.com/images/NocoBaseMindMapLite.png) diff --git a/docs/tr-TR/welcome/introduction/features/2.collection-block.png b/docs/tr-TR/welcome/introduction/features/2.collection-block.png new file mode 100644 index 000000000..93243e645 Binary files /dev/null and b/docs/tr-TR/welcome/introduction/features/2.collection-block.png differ diff --git a/docs/tr-TR/welcome/introduction/features/2.user-root.gif b/docs/tr-TR/welcome/introduction/features/2.user-root.gif new file mode 100644 index 000000000..50742aef3 Binary files /dev/null and b/docs/tr-TR/welcome/introduction/features/2.user-root.gif differ diff --git a/docs/tr-TR/welcome/introduction/index.md b/docs/tr-TR/welcome/introduction/index.md new file mode 100644 index 000000000..7fd411823 --- /dev/null +++ b/docs/tr-TR/welcome/introduction/index.md @@ -0,0 +1,50 @@ +# Tanıtım + +![](https://nocobase.oss-cn-beijing.aliyuncs.com/bbcedd403d31cd1ccc4e9709581f5c2f.png) + +**Not:** 📌 + +NocoBase, geliştirmenin ilk aşamasındadır ve sık sık değişiklik yaparak geliştiriyoruz. Lütfen üretim ortamlarında dikkatli olun. + +## NocoBase Nedir? + +NocoBase, ölçeklenebilirlik öncelikli, açık kaynaklı, kod yazmadan geliştirme yapabileceğiniz bir platformudur. Programlama gerekmez, dakikalar içinde NocoBase ile kendi yönetim sisteminizi oluşturun. + +Anasayfa: +https://www.nocobase.com/ + +Canlı Test: +https://demo.nocobase.com/new + +İletişim: +hello@nocobase.com + +## Özellikler + +- **Açık kaynak ve özgür** + - Apache-2.0 lisansı altında sınırsız ticari kullanım + - Tam kod sahipliği, özel dağıtım, özel ve güvenli veriler + - Yeni ihtiyaçlarınız için özgürce geliştirip geliştirebilirsiniz + - İyi ekolojik destek +- **Güçlü kodsuz yetenek** + - Veri Modeli + - Metin, sayı, ek gibi düzinelerce alan türünü ve bire bir, bire çok gibi çeşitli ilişkilendirme ilişkilerini kullanarak bağımsız veri modelleri oluşturun. + - Blok + - Tablolar, formlar, kanban, takvimler, ayrıntılar vb. gibi blok türlerinin ücretsiz bir kombinasyonunu kullanarak bir sayfadaki verileri görüntüleyin ve değiştirin. + - İşlem + - Daha fazla türe genişletilebilen verileri işlemek için filtreleme, dışa aktarma, ekleme, silme, değiştirme, görüntüleme ve diğer işlemleri destekleyin. + - Erişim Kontrol Listesi(ACL) + - Kullanıcının sistem yapılandırma haklarının, veri eylemi haklarının ve menü erişim haklarının rol tabanlı kontrolü. + - İş akışı + - Verimliliği artırmak için tekrarlayan görevler otomasyonla değiştirilir. Önemli konular için manuel onay gereklidir. +- **Genişletilmiş geliştirme için tasarlandı** + - NocoBase mikro çekirdekli bir mimari kullanır, çeşitli işlevler eklentilerle genişletilir. + - Koa, Sequelize, React, Formily, Ant Design vb. dahil olmak üzere ana akım çerçeveleri ve teknolojileri kullanan Node.js'ye dayalı bir geliştirmedir. + - Aşamalı geliştirme, başlangıç için kolay, yeni başlayanlar için arkadaş canlısı + - Bağlama yok, güçlü bağımlılık yok, herhangi bir kombinasyonda veya uzantıda kullanılabilir, mevcut projelerde kullanılabilir + +## Mimari + +![](https://www.nocobase.com/images/NocoBaseMindMapLite.png) + +[Tam ekran görüntülemek için tıklayın](https://www.nocobase.com/images/NocoBaseMindMap.png) diff --git a/docs/tr-TR/welcome/introduction/when.md b/docs/tr-TR/welcome/introduction/when.md new file mode 100644 index 000000000..61ebc59ec --- /dev/null +++ b/docs/tr-TR/welcome/introduction/when.md @@ -0,0 +1,10 @@ +# NocoBase kimin içindir + +Aşağıdaki ihtiyaçlarınız varsa NocoBase sizin için tasarlanmıştır. + +- Bir iç yönetim sistemi geliştirmek istiyorsanız +- Kodsuz geliştirme ile iş ihtiyaçlarınızın çoğunu karşılamak istiyorsanız +- Kodsuz geliştirme, geliştirici olmayanlar için yeterince basittir; yerel gelişime yakın olacak kadar esnek +- Sisteminizi kolay bir şekilde sürekli geliştirebilirsiniz +- Tam kod ve veri kontrolü ile özel dağıtım +- Ücretsiz kullanabilirsiniz veya daha fazla teknik destek için ödeme yapabilirsiniz diff --git a/docs/tr-TR/welcome/release/index.md b/docs/tr-TR/welcome/release/index.md new file mode 100644 index 000000000..4d4359ba5 --- /dev/null +++ b/docs/tr-TR/welcome/release/index.md @@ -0,0 +1,478 @@ +# Sürüm Notları + +## 2022/10/31 ~ v0.8.0-alpha.1 + +- Starting with v0.8, NocoBase begins to provide an available plugin manager and development documentation. +- [Check out the main changes in v0.8](/welcome/release/v08-changelog) + +## 2022/10/26 ~ v0.7.7-alpha.1 + +- fix(database/formula-field): when formula's field caculate result is 0 it alse will be save ([#962](https://github.com/nocobase/nocobase/issues/962)) +- feat(file-manager): support tencent cos ([#958](https://github.com/nocobase/nocobase/issues/958)) +- fix(plugin-workflow): fix constant schedule trigger time ([#956](https://github.com/nocobase/nocobase/issues/956)) + +## 2022/10/24 ~ v0.7.6-alpha.2 + +- Turkish language ([#939](https://github.com/nocobase/nocobase/issues/939)) +- refactor(plugin-file-manager): move client code into plugin folder and enable path config ([#913](https://github.com/nocobase/nocobase/issues/913)) +- feat: add filter action to collection table ([#953](https://github.com/nocobase/nocobase/issues/953)) +- feat: ui schema cache ([#877](https://github.com/nocobase/nocobase/issues/877)) +- feat: docker optimizing ([#948](https://github.com/nocobase/nocobase/issues/948)) +- fix(plugin-workflow): test changedWithAssociations() ([#950](https://github.com/nocobase/nocobase/issues/950)) +- fix(plugin-workflow): skip time based test ([#951](https://github.com/nocobase/nocobase/issues/951)) +- fix(plugin-workflow): fix schedule trigger bug ([#949](https://github.com/nocobase/nocobase/issues/949)) +- feat: changed with associations ([#943](https://github.com/nocobase/nocobase/issues/943)) +- feat(useSignup): customize success message +- fix(plugin-workflow): fix collection fieldset component ([#942](https://github.com/nocobase/nocobase/issues/942)) +- fix(plugin-workflow): avoid revision with ghost nodes ([#941](https://github.com/nocobase/nocobase/issues/941)) +- fix(plugin-workflow): add req context to processor ([#936](https://github.com/nocobase/nocobase/issues/936)) +- Feat/plugin workflow collection field ([#934](https://github.com/nocobase/nocobase/issues/934)) +- fix(plugin-workflow): fix schedule infinitely trigger when repeat not set ([#926](https://github.com/nocobase/nocobase/issues/926)) +- fix(plugin-workflow): temp disable validation of collection field in node ([#928](https://github.com/nocobase/nocobase/issues/928)) + +## 2022/10/16 ~ v0.7.5-alpha.1 + +- Optimized query performance for relational fields +- Added support for mobile phone number registration and login +- Sequence field type +- Added Japanese and Russian translations +- The page layout supports drag and drop to adjust the column width +- Fixed several bugs + +### Details + +- feat: plugin workflow collection field ([#919](https://github.com/nocobase/nocobase/issues/919)) +- fix(client/route-switch): skip sub routes +- feat: create with array of values ([#912](https://github.com/nocobase/nocobase/issues/912)) +- fix: unbind on error throwing ([#914](https://github.com/nocobase/nocobase/issues/914)) +- fix: appends merge now using primary key ([#911](https://github.com/nocobase/nocobase/issues/911)) +- fix: cannot read properties of undefined (reading 'target') +- feat: limit database identifier ([#908](https://github.com/nocobase/nocobase/issues/908)) +- fix: sync collection field default value ([#907](https://github.com/nocobase/nocobase/issues/907)) +- fix: version judgment is not accurate +- fix(client): tab pane initializers for create form block +- fix: build error +- fix: appends merge includes ([#905](https://github.com/nocobase/nocobase/issues/905)) +- fix: single relation repository appends query issue ([#901](https://github.com/nocobase/nocobase/issues/901)) +- feat(plugin-workflow): add concat calculator (#894) +- feat: improve signin and signup page components +- fix(client/record-picker): support record-picker show format DataPicker (#888) +- fix(client/block-select-collection): fix select collection menu view error (#889) +- fix: unable to submit form during file upload (#892) +- feat(collection-manager): inverse fields can be configured (#883) +- fix(database): fix the index name too long error +- fix(formula): support integer and fix NaN error (#879) +- fix: sort parameter is missing (#849) +- fix: slow join query issued by appends field in find method of repository (#845) +- feat(core/cache): support cache (#876) +- feat: update option must have filter or filterByTk (#847) +- added Russian translation (#840) +- feat(database): add sequence field type (#779) +- fix: can't access pages without permission via url (#826) +- fix(collection-manger): incorrect scope key parameter +- fix: missing RefreshActionInitializer +- fix(collection-manager): o2m is array type +- fix(plugin-system-settings): convert array to json +- fix: transaction cannot be rolled back because it has been finished with state: rollback +- fix(plugin-export): filter non-existent fields +- refactor(resourcer): combine middleware class ([#825](https://github.com/nocobase/nocobase/issues/825)) +- refactor(database): fix some fields and types ([#820](https://github.com/nocobase/nocobase/issues/820)) +- feat: language settings support Japanese +- feat(locale): added Japanese translation ([#813](https://github.com/nocobase/nocobase/issues/813)) +- fix(plugin-workflow): fix value type for DatePicker to moment ([#815](https://github.com/nocobase/nocobase/issues/815)) ([#819](https://github.com/nocobase/nocobase/issues/819)) +- refactor(plugin-workflow): export client calculators registry ([#816](https://github.com/nocobase/nocobase/issues/816)) +- fix: number storage type changed to double ([#810](https://github.com/nocobase/nocobase/issues/810)) +- refactor(server) ([#795](https://github.com/nocobase/nocobase/issues/795)) +- fix(plugin-verification): change provider rate limit error to 429 ([#788](https://github.com/nocobase/nocobase/issues/788)) +- fix(plugin-cm): fix field disappear after failed to update ([#773](https://github.com/nocobase/nocobase/issues/773)) +- fix: fix uiSchema undefined ([#770](https://github.com/nocobase/nocobase/issues/770)) +- fix: translation +- fix(plugin-cm): fix unique option default value to update ([#768](https://github.com/nocobase/nocobase/issues/768)) +- fix(plugin-users): fix update profile 500 ([#766](https://github.com/nocobase/nocobase/issues/766)) ([#767](https://github.com/nocobase/nocobase/issues/767)) +- fix: mysql column in where clause is ambiguous ([#756](https://github.com/nocobase/nocobase/issues/756)) +- feat(plugin-cm): add unique option for base fields ([#745](https://github.com/nocobase/nocobase/issues/745)) +- feat(plugin-verification): add plugin-verification and phone for users ([#722](https://github.com/nocobase/nocobase/issues/722)) +- feat: resize grid columns with drag and drop ([#748](https://github.com/nocobase/nocobase/issues/748)) +- refactor(client): split schema-initializer items into multiple files ([#744](https://github.com/nocobase/nocobase/issues/744)) +- refactor(plugin-workflow): change files mode to 644 ([#755](https://github.com/nocobase/nocobase/issues/755)) +- fix: db version check ([#749](https://github.com/nocobase/nocobase/issues/749)) +- feat: add examples ([#718](https://github.com/nocobase/nocobase/issues/718)) + +## 2022/08/15 ~ v0.7.4-alpha.7 + +### Details + +- fix(collection-manager): update collection without fields + +## 2022/08/12 ~ v0.7.4-alpha.4 + +### New features + +- Field default value + +### Details + +- fix(database): error getting db version number +- fix: record provider required for read pretty +- fix: sync table sort to export (#723) +- feat: full version of the NocoBase dockerfile (#719) +- feat: add examples +- chore: update node ci +- fix(plugin-workflow): fix extend collection (#708) +- fix: DB_TABLE_PREFIX doesn't get applied (#710) +- feat: default value (#679) +- fix: required field delete submit error (#688) (#694) + +## 2022/07/28 ~ v0.7.4-alpha.1 + +### Details + +- fix: append roles to current user (#695) +- fix(client): required for the sub-table field +- fix: date format (#686) +- test(plugin-workflow): skip prompt tests (#692) +- fix: accuracy of percent (#685) +- fix: the database only supports MySQL 8.0.17 and above, SQLite 3.x and PostgreSQL 10+ +- fix(plugin-workflow): adjust await sleep time for test cases (#691) +- feat(plugin-workflow): add assignees config for prompt instruction (#690) +- fix: role export button display (#616) (#666) +- fix: uid validate (#681) +- feat(client): tab icon +- fix(plugin-error-handler): no error message +- fix(client): fieldNames of RecordPicker +- fix: hide password +- refactor: replace react-drag-listview with @dnd-kit/sortable (#660) +- refactor(plugin-users): improve extendibility of middlewares (#677) +- feat: o2m delete not refresh (#646) +- feat: kanban add description (#659) +- fix: field loss enum (#667) +- feat: add ui editor hot key Ctrl+Shift+U (#675) +- fix: calendar change field error (#626) (#671) +- chore: fix eslint not work (#670) +- feat: number precision (#661) +- feat: nginx config (#664) +- feat: form item designer form switch issue (#656) +- fix: wrong operator + +## 2022/07/20 ~ v0.7.3-alpha.1 + +### New features + +- Form validation +- Actions: Print, Refresh + +### Details + +- fix(client): hide modal header +- feat: customizable jwt expiration date +- feat: print action (#652) +- feat: restore action-hooks (#655) +- feat: collections & fields pagination issue (#653) +- fix(core): change proxied agent methods to native (#654) +- feat: remove table field details actions (#638) +- fix: link to default value (#641) +- fix(client): build error +- fix: localStorage is not defined +- feat: support for displaying relational table fields in details or form blocks (#635) +- fix: record picker cannot select from different pages (#623) +- feat(client): plugin toolbar icons and translations +- fix: dragging an element to the left, right, or bottom would cause the element to disappear (#620) +- feat: table action add reload button (#630) +- feat: improve language settings (#627) +- feat: field assignment for custom actions supports string variables (#597) +- fix(client): blocks are deleted when they are dragged below the current block +- fix: skip recursive remove on grid component (#621) +- feat: fix time and collection pagination (#618) +- feat: recordblockinitializers fields pick (#558) +- fix: incorrectly :active background (#607) +- fix: obo table selector (#613) +- feat: form validator (#569) +- fix: table selector (#612) + +## 2022/07/07 ~ v0.7.2-alpha.2 + +- fix(g2plot): import all plots +- fix: field permissions cannot be saved (#605) +- fix(plugin-workflow): fix revision bug (#603) +- fix(plugin-workflow): fix select value (#600) +- fix(plugin-workflow): fix CollectionFieldSelect component (#598) +- feat(plugin-workflow): add association select in calculation (#584) +- feat: function for chart data request +- fix(cli): remove process.env.NODE_OPTIONS + +## 2022/07/05 ~ v0.7.2-alpha.1 + +### New features + +- Fields: Integer field +- Blocks: Display fields of relational collections in blocks +- Plugins: Filter conditions support variables + +### Breaking changes + +- New version does not create foreign key constraints by default, old version will delete all created foreign key constraints after upgrade +- If you installed NocoBase using yarn create before, you need to yarn create again and then execute `yarn nocobase upgrade --raw` + +### Details + +- fix: drop all foreign keys (#576) +- fix(plugin-workflow): fix collection trigger config (#575) +- fix: improve filter item styling +- fix(collection-manager): missing collection manager context +- feat: filter with variable (#574) +- feat(cli): check database version before installation (#572) +- fix(client): comment out useless code +- fix(cli): app start before sync and upgrade +- feat(client): integer field +- fix(database): index invalid (#564) +- fix: export association table data (#561) +- fix(client): maximum call stack size exceeded (#554) +- refactor(plugin-workflow): move client files into plugin (#556) +- fix(database): constraints default to false (#550) +- fix(client): cannot read properties of undefined (reading 'split') +- fix(workflow): merge workflow providers +- fix(workflow): load workflow after application initialization +- fix(plugin-workflow): fix select width (#552) +- feat: compatible with old kanban (#553) +- fix(client): consider explicitly re-exporting to resolve the ambiguity +- feat: display association fields (#512) +- Fix(plugin workflow) (#549) +- fix: update mysql port (#548) +- fix: export of relation blocks (#546) +- fix(plugin-workflow): clear options when change collection (#547) +- feat(plugin-workflow): add race mode (#542) +- fix(client): change toArr to _.castArray in select component (#543) + +## 2022/06/26 ~ v0.7.1-alpha.7 + +### New features + +- Fields: Formula、Relationships(o2o, o2m, m2o, m2m) +- Blocks: Charts(g2plot) +- Plugins: Audit logs, Export, Workflow(schedule trigger) + +### Breaking changes + +- The percentage field stores the original value. For example, the old version stored 1% as 1 and the new version stores 1% as 0.01 +- Remove sub-table field and replace it with one-to-many relationship +- If the NocoBase application was previously installed using yarn create, you need to yarn create again, and then execute yarn nocobase upgrade + +### Details + +- fix(cli): upgrade from docker +- chore(create-nocobase-app): fix some bugs (#538) +- feat: relationship fields are loaded on demand +- fix: destroy collection fields (#536) +- feat(plugin-workflow): add delay node type (#532) +- refactor: client application (#533) +- fix: missing transaction (#531) +- fix: add ellipsis property to record picker (#527) +- fix: remove pattern without form item (#528) +- fix(client): update only fields in the form +- fix(client): remove z-index +- fix(plugin-workflow): set current when update (#526) +- fix(client): non-empty judgment +- fix: order nulls last (#519) +- fix(client): close the pop-up after request +- fix: action loading, refresh context, form submit and validate (#523) +- fix: field pattern (#520) +- fix(plugin-workflow): fix searchable select min-width (#524) +- fix: template with fields only (#517) +- fix(plugin-workflow): fix update workflow current property (#521) +- feat: improve chart component +- refactor(plugin-workflow): abstract to classes (#515) +- feat: column sortable and form item pattern (#518) +- feat(client): display option value +- feat(client): hide drawer header +- fix(audit-logs): operator does not exist: character varying = integer +- fix(custom-request): support string/json templates (#514) +- fix(cli): missing await +- feat: add block title (#513) +- fix: remove collections & fields from db (#511) +- fix(cli): upgrade error in node v14 +- feat: improve migrations (#510) +- fix(client): improve datepicker component, date with time zone, gmt support +- fix: datepicker with timezone +- fix(client): consolidate usage of date/time as UTC in transfering (#509) +- fix: formula bug +- fix: default exportable fields (#506) +- fix(audit-logs): sort by createdAt +- fix(plugin-export): allow to configure in acl +- fix: sign in/sign up with enter key +- fix(client): percent precision +- feat: association field block (#493) +- feat: plugin export (#479) +- fix: create or delete collection error (#501) +- feat: update collections & fields (#500) +- fix: rollback when field creation fails (#498) +- fix(client): set `dropdownMatchSelectWidth` to false globally (#497) +- fix(client): no-key warning in user menu items (#496) +- Feat(plugin workflow): cron field for schedule trigger configuration (#495) +- feat: audit logs (#494) +- fix(client): language settings +- feat(client): improve locale +- refactor(plugin-workflow): add revision column to execution (#491) +- fix(plugin-multi-app-manager): fix pg cannot create database block tests +- refactor(database): hook proxy (#402) +- feat: chart blocks (#484) +- refactor(plugin workflow): support number in repeat config for schedule +- chore(debug): add debug config (#475) +- fix: has one bug +- feat: relationships (#473) +- fix(plugin-workflow): fix collection trigger transaction (#474) +- fix(plugin-workflow): temporary solution for collection trigger conditions +- fix: markdown component (#469) +- fix: formula field and percent field (#467) +- fix(plugin-workflow): fix update workflow action (#464) +- fix(acl): skip when field does not exist +- fix: update formula field and percent field (#461) +- fix(client): export useSignin and useSignup +- fix(ci): node_version = 14 +- fix(cli): yarn install --production error +- fix(client): build error +- feat: add formula field type (#457) +- fix: the details of the associated data in the subtable are not displayed +- fix(plugin-workflow): fix languages (#451) +- fix: afterSync hook not triggered (#450) + +## 2022/06/01 ~ v0.7.0-alpha.83 + +- fix: default value of time zone +- fix(database): add timezone support +- docs(various): Improve readability (#447) +- fix(client): datetime with timezone +- feat(plugin-file-manager): record the creator of the attachment +- feat: custom request (#439) +- feat(plugin workflow): schedule trigger (#438) +- feat(database): db migrator (#432) +- fix(client): select component cannot be opened in sub-table block (#431) +- fix: error message "error:0308010C:digital envelope routines::unsupported +- docs(github): change to markdown format (#430) +- fix(cli): typo (#429) + +### New Features + +- Core: db migrator + +## 2022/05/26 ~ v0.7.0-alpha.82 + +- feat(client,sdk): improve api client + +### Breaking changes + +There are major changes to the `APIClient` API, see details [JavaScript SDK](./development/http-api/javascript-sdk.md) + +## 2022/05/25 ~ v0.7.0-alpha.81 + +- feat: add create-plugin command (#423) +- fix: "typescript": "4.5.5" +- docs: update documentation +- fix(client): filter menu item schema by permissions +- fix(database): cannot read properties of null (reading 'substring') +- fix(client): add description +- fix(client): clone schema before insert +- feat(client): add a description to the junction collection field +- fix(devtools): unexpected token '.' + +## 2022/05/24 ~ v0.7.0-alpha.78 + +- fix(client): add RemoteDocumentTitleProvider +- fix(client): incomplete calendar events +- fix(plugin-users): add translations (#416) + +## 2022/05/23 ~ v0.7.0-alpha.59 + +- feat(docs): add alert message +- fix(create-nocobase-app): storage path error +- fix(client): improve translation +- fix(cli): nocobase test command --db-clean option is invalid +- refactor(plugin-workflow): change column type of executed from boolean to integer (#411) + +## 2022/05/22 ~ v0.7.0-alpha.58 + +- fix: 204 no content response (#378) +- feat: destroy association field after target collection destroy (#376) +- fix(type): use sequelize native Transactionable instead of TransactionAble (#410) +- fix(plugin-workflow): remove previous listeners when collection changed in config (#409) +- fix(plugin-acl): missing pagination parameters (#394) +- feat(client): add custom action (#396) +- refactor(plugin-workflow): multiple instances and event management (fix #384) (#408) +- feat(cli): --db-sync options +- fix(client): pagination dropdown menu is blocked (#398) +- feat: display version number (#386) +- fix: missing isTruly/isFalsy filter operators (#390) +- fix(client): reset page number to first page (#399) + +## 2022/05/19 ~ v0.7.0-alpha.57 + +### New features +- Packaging tool `@nocobase/build` +- CLI `@nocobase/cli` +- devtools `@nocobase/devtools` +- JavaScript SDK `@nocobase/sdk` +- Documents(v0.7) + +### Bug fixes & improvements +- `@nocobase/preset-nocobase` +- create scaffolding `create-nocobase-app` +- Documents theme `dumi-theme-nocobase` + +### Breaking changes + +📢 Previously created projects need to be recreated. + +## 2022/05/14 ~ v0.7.0-alpha.34 + +- feat: add plugins:getPinned action api +- fix(plugin workflow): cannot get job result properties (#382) +- feat: exist on server start throw error (#374) +- chore: application options (#375) +- fix: not in operator with null value record (#377) + +## 2022/05/13 ~ v0.7.0-alpha.33 + +- fix: link-to field data scope error (#1337) +- feat(plugin workflow): revisions (#379) +- fix(database): fix option-parser include list index (#371) +- fix(plugin-workflow): fix duplicated description in fields values (#368) +- fix(database): fix type and transaction in repository (#366) +- fix(plugin workflow): fix transaction of execution (#364) + +## 2022/05/05 ~ v0.7.0-alpha.30 + +- fix(client): upgrade formily packages +- fix(client): setFormValueChanged must be defined + +## 2022/05/01 ~ v0.7.0-alpha.27 + +- fix: use wrapper when greater than one column +- fix: props for CreateFormBlockInitializers +- fix: add schema initializer icon +- fix: plugin workflow (#349) +- fix: db:sync not working (#348) +- fix(plugin-workflow): fix trigger bind logic to avoid duplication (#347) +- fix(plugin workflow) (#346) +- fix: action open mode +- fix: menu url style (#344) +- feat: action loading +- fix: compile the label field +- fix: invalid drag and drop sort + +## 2022/04/25 ~ v0.7.0-alpha.16 + +- fix: cannot find module mkdirp (#330) +- fix(plugin workflow): UX issues (#329) +- fix(plugin-file-manager): test failed +- fix(app-server): dist options + +## 2022/04/25 ~ v0.7.0-alpha.0 + +- Alpha Version + +## 2021/10/07 ~ v0.5.0 + +- The second preview version + +## 2021/04/07 ~ v0.4.0 + +- The first preview version diff --git a/docs/tr-TR/welcome/release/v08-changelog.md b/docs/tr-TR/welcome/release/v08-changelog.md new file mode 100644 index 000000000..97cee2d6e --- /dev/null +++ b/docs/tr-TR/welcome/release/v08-changelog.md @@ -0,0 +1,221 @@ +# v0.8:Plugin manager & docs + +Starting with v0.8, NocoBase begins to provide an available plugin manager and development documentation. Here are the main changes in v0.8. + +## Tweaks to the top right corner of the interface + +- UI Editor +- Plugin Manager +- Settings Center +- Personal Center + + + +## The new plugin manager + +v0.8 provides a powerful plugin manager for managing plugins in a no-code way. + +### Plugin manager flow + + + +### Plugin Manager interface + +Currently it is mainly used for disabling, activating and deleting local plugins. Built-in plugins cannot be deleted. + + + +### Plugin Manager command + +In addition to being able to activate and disable plugins from the no-code interface, you can also manage plugins more completely from the command line. + +``` +# Create a plugin +yarn pm create hello +# Register the plugin +yarn pm add hello +# Activate the plugin +yarn pm enable hello +# Disable the plugin +yarn pm disable hello +# Remove the plugin +yarn pm remove hello + +``` + +Note: Releases and upgrades for plugins will be supported in subsequent releases. + +``` +# Publish the plugin +yarn pm publish hello +# Publish the plugin +yarn pm upgrade hello + +``` + +For more plugin examples, see [packages/samples](https://github.com/nocobase/nocobase/tree/main/packages/samples). + +## Changes of plugin + +### Plugin’s directory structure + +``` +|- /hello + |- /src + |- /client # Plugin client + |- /server # Plugin server + |- client.d.ts + |- client.js + |- package.json # Plugin package information + |- server.d.ts + |- server.js + +``` + +### Plugin’s name specification + +NocoBase plugin is also an NPM package, the correspondence rule between plugin name and NPM package name is `${PLUGIN_PACKAGE_PREFIX}-${pluginName}`. + +`PLUGIN_PACKAGE_PREFIX` is the plugin package prefix, which can be customized in .env, [click here for PLUGIN_PACKAGE_PREFIX description](https://www.notion.so/api/env#plugin_package_prefix). + +For example, a project named `my-nocobase-app` adds the `hello` plugin with package name `@my-nocobase-app/plugin-hello`. + +`PLUGIN_PACKAGE_PREFIX` is configured as follows. + +``` +PLUGIN_PACKAGE_PREFIX=@nocobase/plugin-,@nocobase/preset-,@my-nocobase-app/plugin- + +``` + +The correspondence between plugin names and package names is + +- `users` plugin package name is `@nocobase/plugin-users` +- `nocobase` plugin package name is `@nocobase/preset-nocobase` +- `hello` plugin package named `@my-nocobase-app/plugin-hello` + +### Plugin’s lifecycle + +v0.8 provides a more complete approach to the plugin lifecycle. + +``` +import { InstallOptions, Plugin } from '@nocobase/server'; + +export class HelloPlugin extends Plugin { + afterAdd() { + // After the plugin has been added via pm.add + } + + beforeLoad() { + // Before all plugins are loaded, generally used to register classes and event listeners + } + + async load() { + // Load configuration + } + + async install(options?: InstallOptions) { + // Install logic + } + + async afterEnable() { + // After activation + } + + async afterDisable() { + // After disable + } + + async remove() { + // Remove logic + } +} + +export default HelloPlugin; + +``` + +### Front- and back-end entrance for plugins + +The lifecycle of the plugin is controlled by the server + +``` +import { Application } from '@nocobase/server'; + +const app = new Application({ + // ... +}); + +class MyPlugin extends Plugin { + afterAdd() {} + beforeLoad() {} + load() {} + install() {} + afterEnable() {} + afterDisable() {} + remove() {} +} + +app.plugin(MyPlugin, { name: 'my-plugin' }); + +``` + +The client side of the plugin exists as Context.Provider (similar to Middleware on the server side) + +``` +import React from 'react'; +import { Application } from '@nocobase/client'; + +const app = new Application({ + apiClient: { + baseURL: process.env.API_BASE_URL, + }, + dynamicImport: (name: string) => { + return import(`../plugins/${name}`); + }, +}); + +// When you visit the /hello page, it displays Hello world! +const HelloProvider = React.memo((props) => { + const location = useLocation(); + if (location.pathname === '/hello') { + return
    Hello world!
    + } + return <>{props.children} +}); + +app.use(HelloProvider); + +``` + +## Custom business code + +v0.7 plugins are not complete, custom business code may be scattered in `packages/app/client` and `packages/app/server`, which is not conducive to upgrade and maintenance. v0.8 recommends organizing as a plugin package and using `yarn pm` to manage plugins. + +## More complete documentation is provided + +- **Welcome**: a quick look at NocoBase +- **Manual**: learn more about the core features provided by the NocoBase platform +- **Plugin Development Tutorial**: Advanced dive into plugin development +- **API Reference**: Check the API usage during plugin development +- **Client Components Library** (in preparation): provides examples and usage of NocoBase components + +## More plugin examples are provided + +- [command](https://github.com/nocobase/nocobase/tree/develop/packages/samples/command) +- [custom-block](https://github.com/nocobase/nocobase/tree/develop/packages/samples/custom-block) +- [custom-page](https://github.com/nocobase/nocobase/tree/develop/packages/samples/custom-page) +- [custom-signup-page](https://github.com/nocobase/nocobase/tree/develop/packages/samples/custom-signup-page) +- [hello](https://github.com/nocobase/nocobase/tree/develop/packages/samples/hello) +- [ratelimit](https://github.com/nocobase/nocobase/tree/develop/packages/samples/ratelimit) +- [shop-actions](https://github.com/nocobase/nocobase/tree/develop/packages/samples/shop-actions) +- [shop-events](https://github.com/nocobase/nocobase/tree/develop/packages/samples/shop-events) +- [shop-i18n](https://github.com/nocobase/nocobase/tree/develop/packages/samples/shop-i18n) +- [shop-modeling](https://github.com/nocobase/nocobase/tree/develop/packages/samples/shop-modeling) + +## Other new features and functionality + +- Import from Excel +- Bulk Update & Edit +- Graphical collection +- Workflow support for viewing execution history +- JSON field diff --git a/docs/tr-TR/welcome/release/v08-changelog/pm-flow.svg b/docs/tr-TR/welcome/release/v08-changelog/pm-flow.svg new file mode 100644 index 000000000..d15cf800c --- /dev/null +++ b/docs/tr-TR/welcome/release/v08-changelog/pm-flow.svg @@ -0,0 +1 @@ +
    Local
    pm.create
    Marketplace
    pm.publish
    NPM registry
    Extracting client files
    pm.add
    app/client plugins
    pm.enable
    pm.disable
    pm.remove
    \ No newline at end of file diff --git a/docs/tr-TR/welcome/release/v08-changelog/pm-ui.jpg b/docs/tr-TR/welcome/release/v08-changelog/pm-ui.jpg new file mode 100644 index 000000000..e6a74f84a Binary files /dev/null and b/docs/tr-TR/welcome/release/v08-changelog/pm-ui.jpg differ diff --git a/docs/tr-TR/welcome/release/v08-changelog/topright.jpg b/docs/tr-TR/welcome/release/v08-changelog/topright.jpg new file mode 100644 index 000000000..96a31b181 Binary files /dev/null and b/docs/tr-TR/welcome/release/v08-changelog/topright.jpg differ