Skip to content

Commit 34b61f9

Browse files
Rafał Dzięgielewskigitbook-bot
authored andcommitted
GITBOOK-102: Relations Feature
1 parent 8d972a2 commit 34b61f9

File tree

6 files changed

+348
-1
lines changed

6 files changed

+348
-1
lines changed
Loading
Loading
Loading

SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
* [Action](basics/action.md)
3636
* [Property](basics/property.md)
3737
* [Features](basics/features/README.md)
38+
* [Relations](basics/features/relations.md)
3839
* [Upload](basics/features/upload.md)
3940
* [Logger](basics/features/logger.md)
4041
* [Import & Export](basics/features/import-and-export.md)

basics/features/relations.md

Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
---
2+
description: '@adminjs/relations'
3+
---
4+
5+
# Relations
6+
7+
`@adminjs/relations` is a feature which allows you to manage `one-to-many` and `many-to-many` relations within your admin panel.
8+
9+
As of version `1.0.0` it supports:
10+
11+
* listing multiple `one-to-many` relations with pagination but no filters in the details view of a record,
12+
* listing multiple `many-to-many` relations with pagination but no filters in the details view of a record,
13+
* editing and creating `one-to-many` relations,
14+
* editing `many-to-many` relations,
15+
* deleting records listed in `one-to-many` table (if you want to only remove the relation, you can just modify the target record)
16+
* deleting relations in junction table of `many-to-many` relation **or** deleting the target record of a `many-to-many` relation (it also deleted the relation in junction table)
17+
* navigating to details view of a target relation,
18+
* adding an existing record to a `many-to-many` relation or creating a new record which will be assigned to your `many-to-many` relation.
19+
20+
<div>
21+
22+
<figure><img src="../../.gitbook/assets/Screenshot 2023-04-26 at 12.34.31.png" alt=""><figcaption><p>One-To-Many List</p></figcaption></figure>
23+
24+
25+
26+
<figure><img src="../../.gitbook/assets/Screenshot 2023-04-26 at 12.36.20.png" alt=""><figcaption><p>Many-To-Many List</p></figcaption></figure>
27+
28+
29+
30+
<figure><img src="../../.gitbook/assets/Screenshot 2023-04-26 at 12.36.39.png" alt=""><figcaption><p>Many-To-Many Modal</p></figcaption></figure>
31+
32+
</div>
33+
34+
## Installation
35+
36+
`@adminjs/relations` is a premium feature which can be purchased at [https://cloud.adminjs.co](https://cloud.adminjs.co)
37+
38+
All premium features currently use **One Time Payment** model and you can use them in all apps that belong to you. Once you purchase the addon, you will receive a license key which you should provide in `@adminjs/relations` configuration in your application's code.
39+
40+
Installing the library:
41+
42+
```bash
43+
$ yarn add @adminjs/relations
44+
```
45+
46+
The license key should be provided to `owningRelationSettingsFeature`:
47+
48+
```typescript
49+
owningRelationSettingsFeature({
50+
licenseKey: process.env.LICENSE_KEY,
51+
// the rest of the config
52+
})
53+
```
54+
55+
`targetRelationSettingsFeature` does not require a license key as it's role is mostly utility-only. The documentation below describes the configuration objects and setup instructions in more detail.
56+
57+
If you encounter any issues or require help installing the package please contact us at [email protected] or through our Discord server.
58+
59+
## Usage
60+
61+
Similarly to other features, the `@adminjs/relations` feature has to be imported into `features` configuration section of your resource. `@adminjs/relations` exports two separate feature that you must configure in order for the functionality to work:
62+
63+
* `owningRelationSettingsFeature` is used to configure the relations that you will want to manage later,
64+
* `targetRelationSettingsFeature` doesn't require any configuration, but it has to be included in targetted resource in order for redirects and `many-to-many` assignments to work properly.
65+
66+
The two features will be explained in more detail in the later parts of this guide.
67+
68+
### Example Database Structure
69+
70+
The usage guide will be based on sample database tables which can be represented by the following interfaces:
71+
72+
```typescript
73+
interface IOrganization {
74+
id: number;
75+
name: string;
76+
}
77+
78+
interface ITeam {
79+
id: number;
80+
name: string;
81+
}
82+
83+
interface IPerson {
84+
id: number;
85+
name: string;
86+
email: string;
87+
organizationId: number;
88+
}
89+
90+
interface ITeamMember {
91+
id: number;
92+
personId: number;
93+
teamId: number;
94+
}
95+
96+
interface IOffice {
97+
id: number;
98+
name: string;
99+
address: string;
100+
organizationId: string;
101+
}
102+
103+
/*
104+
Person belongs to 1 Organization
105+
Organization has many Persons
106+
Office belongs to 1 Organization
107+
Organization has many Offices
108+
Person belongs to multiple Teams through TeamMember
109+
Team belongs to multiple Persons through TeamMember
110+
*/
111+
```
112+
113+
`@adminjs/relations` is adapter-agnostic which means you can use it regardless of the database adapter you had installed. Nevertheless, some ORMs automatically generate and manage junction tables for you without you having to actually create entities for them in your codebase. This will not work with AdminJS and you will have to create actual entities for junction tables and register them as AdminJS resources since AdminJS uses them to find your `M:N` records.
114+
115+
```typescript
116+
const admin = new AdminJS({
117+
resources: [
118+
createOrganizationResource(),
119+
createPersonResource(),
120+
createOfficeResource(),
121+
createTeamResource(),
122+
createTeamMemberResource(),
123+
],
124+
})
125+
```
126+
127+
{% hint style="warning" %}
128+
AdminJS requires every resource to have a primary key column, this includes junction tables.
129+
{% endhint %}
130+
131+
### Feature Options
132+
133+
Below you can find feature options of `owningRelationSettingsFeature` which you can use for reference.
134+
135+
```typescript
136+
enum RelationType {
137+
OneToMany = 'one-to-many',
138+
ManyToMany = 'many-to-many',
139+
}
140+
141+
type RelationsFeatureConfig = {
142+
/* Your ComponentLoader instance, ideally you will create it in a separate file
143+
and import where it's needed. Documentation: https://docs.adminjs.co/ui-customization/writing-your-own-components */
144+
componentLoader: ComponentLoader;
145+
/* Your license key */
146+
licenseKey: string;
147+
/* A configuration object for relations that will be managable in a given resource. */
148+
relations: {
149+
/* A name of a relation. It will be used as a name in tabbed table (see screenshots above) */
150+
[resourceId: string]: {
151+
/* A relation type which can be either `one-to-many` or `many-to-many` */
152+
type: RelationType;
153+
/* A junction resource/table configuration. It is only required for `many-to-many` */
154+
junction?: {
155+
/* A "joinKey" inside junction table. If configuring for "Team", it can be "teamId". */
156+
joinKey: string;
157+
/* An "inverseJoinKey" inside junction table. If "Team" has a M:N relation with "Person", it can be "personId" */
158+
inverseJoinKey: string;
159+
/* A resource ID of the junction table, for example: "TeamMember" */
160+
throughResourceId: string;
161+
};
162+
/* A target resource/table configuration. A target is a resource which is listed in the table. */
163+
target: {
164+
/* A "resourceId" of the target. Example: "Person" */
165+
resourceId: string;
166+
/* A "joinKey" of the target. Example: "organizationId" */
167+
joinKey?: string;
168+
};
169+
}
170+
};
171+
/* An optional field which allows you to specify a different property key which will be used
172+
to display relations table. By default it adds `relations` to details view of your resource. */
173+
propertyKey?: string;
174+
};
175+
```
176+
177+
### One-To-Many
178+
179+
According to the database structure described above as well as the presented configuration options of `owningRelationSettingsFeature`, this is how you can add this feature to `Organization` resource which can have many `Persons` and `Offices`
180+
181+
{% code title="organization.resource.ts" %}
182+
```typescript
183+
import { owningRelationSettingsFeature, type RelationType } from '@adminjs/relations'
184+
import { componentLoader } from './component-loader.js';
185+
import { Organization } from './models/index.js';
186+
187+
export const createOrganizationResource = () => ({
188+
resource: Organization,
189+
features: [
190+
owningRelationSettingsFeature({
191+
componentLoader,
192+
licenseKey: process.env.LICENSE_KEY,
193+
relations: {
194+
persons: {
195+
type: RelationType.OneToMany,
196+
target: {
197+
joinKey: 'organizationId',
198+
resourceId: 'Person',
199+
},
200+
},
201+
offices: {
202+
type: RelationType.OneToMany,
203+
target: {
204+
joinKey: 'organizationId',
205+
resourceId: 'Office',
206+
},
207+
},
208+
},
209+
}),
210+
],
211+
});
212+
```
213+
{% endcode %}
214+
215+
Additionally, in your `Office` and `Person` resources you will have to add `targetRelationSettingsFeature`:
216+
217+
{% code title="office.resource.ts" %}
218+
```typescript
219+
import { targetRelationSettingsFeature } from '@adminjs/relations';
220+
import { Office } from './models/index.js';
221+
222+
export const createOfficeResource = () => ({
223+
resource: Office,
224+
features: [targetRelationSettingsFeature()],
225+
});
226+
```
227+
{% endcode %}
228+
229+
{% code title="person.resource.ts" %}
230+
```typescript
231+
import { targetRelationSettingsFeature } from '@adminjs/relations';
232+
import { Person } from './models/index.js';
233+
234+
export const createPersonResource = () => ({
235+
resource: Person,
236+
features: [targetRelationSettingsFeature()],
237+
});
238+
```
239+
{% endcode %}
240+
241+
If you configure your resources as shown above, you should be able to see `Persons` and `Offices` tabs in your `Organization` record's details view.
242+
243+
### Many-To-Many
244+
245+
The example below shows how you can configure a `many-to-many` relation between `Team` and `Person` through `TeamMember`.
246+
247+
{% code title="team.resource.ts" %}
248+
```typescript
249+
import { owningRelationSettingsFeature, type RelationType } from '@adminjs/relations';
250+
import { Team } from './models/index.js';
251+
import { componentLoader } from './component-loader.js';
252+
253+
export const createTeamResource = () => ({
254+
resource: Team,
255+
options: {
256+
navigation: { icon: 'Users' },
257+
},
258+
features: [
259+
owningRelationSettingsFeature({
260+
componentLoader,
261+
licenseKey: process.env.LICENSE_KEY,
262+
relations: {
263+
members: {
264+
type: RelationType.ManyToMany,
265+
junction: {
266+
joinKey: 'teamId',
267+
inverseJoinKey: 'personId',
268+
throughResourceId: 'TeamMember',
269+
},
270+
target: {
271+
resourceId: 'Person',
272+
},
273+
},
274+
},
275+
}),
276+
],
277+
});
278+
```
279+
{% endcode %}
280+
281+
Additionally, in your `Person` resource you will have to make sure to add `targetRelationSettingsFeature`. Of course, if you had added it before you don't have to add it multiple times.
282+
283+
{% code title="person.resource.ts" %}
284+
```typescript
285+
import { targetRelationSettingsFeature } from '@adminjs/relations';
286+
import { Person } from './models/index.js';
287+
288+
export const createPersonResource = () => ({
289+
resource: Person,
290+
features: [targetRelationSettingsFeature()],
291+
});
292+
```
293+
{% endcode %}
294+
295+
### Role Based Access Control
296+
297+
By default all actions related to managing the relations will be available for everyone. You can modify the accessibility in the same way you modify accessibility of your custom actions.
298+
299+
`@adminjs/relations` introduces the following new actions to your resource:
300+
301+
* `findRelation` is used to list `one-to-many` and `many-to-many` records from the target resource,
302+
* `addManyToManyRelation` is used to add existing records to a junction table for `many-to-many` relations
303+
* `deleteRelation` is used to delete a record from a junction table, deleting the relation in the process, but leaving both records
304+
305+
Taking `Team` resource from above as an example, you can allow these actions only for users with role `Admin` by doing the following changes:
306+
307+
```typescript
308+
import { owningRelationSettingsFeature, type RelationType } from '@adminjs/relations';
309+
import { Team } from './models/index.js';
310+
import { componentLoader } from './component-loader.js';
311+
312+
const onlyForAdmin = ({ currentAdmin }) => currentAdmin.role === 'Admin';
313+
314+
export const createTeamResource = () => ({
315+
resource: Team,
316+
options: {
317+
navigation: { icon: 'Users' },
318+
actions: {
319+
findRelation: { isAccessible: onlyForAdmin },
320+
addManyToManyRelation: { isAccessible: onlyForAdmin },
321+
deleteRelation: { isAccessible: onlyForAdmin },
322+
},
323+
},
324+
features: [
325+
owningRelationSettingsFeature({
326+
componentLoader,
327+
licenseKey: process.env.LICENSE_KEY,
328+
relations: {
329+
members: {
330+
type: RelationType.ManyToMany,
331+
junction: {
332+
joinKey: 'teamId',
333+
inverseJoinKey: 'personId',
334+
throughResourceId: 'TeamMember',
335+
},
336+
target: {
337+
resourceId: 'Person',
338+
},
339+
},
340+
},
341+
}),
342+
],
343+
});
344+
```
345+
346+
You can read more about RBAC in AdminJS in [this tutorial](../../tutorials/adding-role-based-access-control.md).

deployment/generate-api-key-and-secret.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Select "Generate API Key" and you should soon see a new screen which allows you
1010

1111
<figure><img src="../.gitbook/assets/Screenshot 2022-11-23 at 10.18.04.png" alt=""><figcaption><p>API Key &#x26; Secret</p></figcaption></figure>
1212

13-
Once you download the file, you can proceed to [deploy your application](deploying-the-application-using-cli.md) using API Key/Secret pair.
13+
Once you download the file, you can proceed to [deploy your application](broken-reference) using API Key/Secret pair.
1414

1515
{% hint style="warning" %}
1616
Choosing to "Generate API Key" again invalidates existing API Key/Secret pair.

0 commit comments

Comments
 (0)