Skip to content

Commit a861d94

Browse files
authored
Merge pull request #2 from devforth/fix/broke
refactor: encapsulate junction resource handling in ManyToManyPlugin …
2 parents ed2c294 + 7ce18a9 commit a861d94

File tree

1 file changed

+67
-41
lines changed

1 file changed

+67
-41
lines changed

index.ts

Lines changed: 67 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import { AdminForthPlugin, Filters } from "adminforth";
22
import type { IAdminForth, IHttpServer, AdminForthResourcePages, AdminForthResourceColumn, AdminForthDataTypes, AdminForthResource } from "adminforth";
33
import type { PluginOptions } from './types.js';
44

5-
let junctionResource = null;
6-
let linkedColumnNameInJunctionResource = null;
7-
let resourceColumnNameInJunctionResource = null;
8-
export default class extends AdminForthPlugin {
5+
export default class ManyToManyPlugin extends AdminForthPlugin {
96
options: PluginOptions;
7+
private junctionResource: AdminForthResource | null = null;
8+
private linkedColumnNameInJunctionResource: string | null = null;
9+
private resourceColumnNameInJunctionResource: string | null = null;
1010

1111
constructor(options: PluginOptions) {
1212
super(options, import.meta.url);
@@ -27,23 +27,29 @@ export default class extends AdminForthPlugin {
2727
}
2828
}
2929
if ( wasLinkedResourceFound && wasCurrentResourceFound ) {
30-
junctionResource = resource;
30+
this.junctionResource = resource;
3131
break;
3232
}
3333
}
34-
if (!junctionResource) {
35-
throw new Error(`Junction resource not found for many-to-many relation between ${resourceConfig.resourceId} and ${this.options.linkedResourceId}`);
34+
if (!this.junctionResource) {
35+
throw new Error(
36+
`Junction resource not found for many-to-many relation between ${resourceConfig.resourceId} and ${this.options.linkedResourceId}. ` +
37+
`Checked resources: ${this.adminforth.config.resources.map(r => r.resourceId).join(", ")}`
38+
);
3639
}
40+
this.linkedColumnNameInJunctionResource = this.junctionResource.columns.find(c => c.foreignResource?.resourceId === this.options.linkedResourceId)?.name || null;
41+
this.resourceColumnNameInJunctionResource = this.junctionResource.columns.find(c => c.foreignResource?.resourceId === resourceConfig.resourceId)?.name || null;
3742

38-
linkedColumnNameInJunctionResource = junctionResource.columns.find(c => c.foreignResource?.resourceId === this.options.linkedResourceId)?.name;
39-
resourceColumnNameInJunctionResource = junctionResource.columns.find(c => c.foreignResource?.resourceId === resourceConfig.resourceId)?.name;
43+
if (!this.linkedColumnNameInJunctionResource || !this.resourceColumnNameInJunctionResource) {
44+
throw new Error(`Junction resource is missing foreign key columns for relation ${resourceConfig.resourceId} <-> ${this.options.linkedResourceId}`);
45+
}
4046

4147
const pluginFrontendOptions = {
4248
pluginInstanceId: this.pluginInstanceId,
4349
resourceId: resourceConfig.resourceId,
4450
linkedResourceId: this.options.linkedResourceId,
45-
junctionResourceId: junctionResource ? junctionResource.resourceId : null,
46-
linkedColumnName: linkedColumnNameInJunctionResource,
51+
junctionResourceId: this.junctionResource.resourceId,
52+
linkedColumnName: this.linkedColumnNameInJunctionResource,
4753
resourcePrimaryKeyColumnName: resourceConfig.columns.find(c => c.primaryKey)?.name,
4854
}
4955

@@ -86,10 +92,10 @@ export default class extends AdminForthPlugin {
8692
if ( recordWithVirtualColumns[`many2many_${this.pluginInstanceId}`] ) {
8793
for(const linkedId of recordWithVirtualColumns[`many2many_${this.pluginInstanceId}`]) {
8894
await this.adminforth.createResourceRecord({
89-
resource: junctionResource,
95+
resource: this.junctionResource,
9096
record: {
91-
[resourceColumnNameInJunctionResource]: recordId,
92-
[linkedColumnNameInJunctionResource]: linkedId,
97+
[this.resourceColumnNameInJunctionResource]: recordId,
98+
[this.linkedColumnNameInJunctionResource]: linkedId,
9399
},
94100
adminUser
95101
});
@@ -102,27 +108,28 @@ export default class extends AdminForthPlugin {
102108

103109
resourceConfig.hooks.edit.beforeSave.push(async ({recordId, updates, adminUser }: { recordId: any, updates: any, adminUser: any }) => {
104110
if ( updates[`many2many_${this.pluginInstanceId}`] ) {
105-
const existingJunctionRecords = await this.adminforth.resource(junctionResource.resourceId).list([Filters.EQ(resourceColumnNameInJunctionResource, recordId)]);
111+
const existingJunctionRecords = await this.adminforth.resource(this.junctionResource.resourceId).list([Filters.EQ(this.resourceColumnNameInJunctionResource, recordId)]);
112+
const junctionPkName = this.junctionResource.columns.find(c => c.primaryKey)?.name;
106113
const updatedLinkedIds = updates[`many2many_${this.pluginInstanceId}`];
107114
for(const jr of existingJunctionRecords) {
108-
const linkedId = jr[linkedColumnNameInJunctionResource];
115+
const linkedId = jr[this.linkedColumnNameInJunctionResource];
109116
if ( !updatedLinkedIds.includes(linkedId) ) {
110117
await this.adminforth.deleteResourceRecord({
111-
resource: junctionResource,
112-
recordId: jr[junctionResource.columns.find(c => c.primaryKey).name],
118+
resource: this.junctionResource,
119+
recordId: jr[junctionPkName],
113120
record: jr,
114121
adminUser
115122
});
116123
}
117124
}
118125
for(const linkedId of updatedLinkedIds) {
119-
const alreadyExists = existingJunctionRecords.find(jr => jr[linkedColumnNameInJunctionResource] === linkedId);
126+
const alreadyExists = existingJunctionRecords.find(jr => jr[this.linkedColumnNameInJunctionResource] === linkedId);
120127
if ( !alreadyExists ) {
121128
await this.adminforth.createResourceRecord({
122-
resource: junctionResource,
129+
resource: this.junctionResource,
123130
record: {
124-
[resourceColumnNameInJunctionResource]: recordId,
125-
[linkedColumnNameInJunctionResource]: linkedId,
131+
[this.resourceColumnNameInJunctionResource]: recordId,
132+
[this.linkedColumnNameInJunctionResource]: linkedId,
126133
},
127134
adminUser
128135
});
@@ -135,24 +142,35 @@ export default class extends AdminForthPlugin {
135142
// ** HOOKS FOR DELETE **//
136143
if (!this.options.dontDeleteJunctionRecords) {
137144
resourceConfig.hooks.delete.beforeSave.push(async ({ recordId, record, adminUser }: { recordId: any, record: any, adminUser: any }) => {
138-
const existingJunctionRecords = await this.adminforth.resource(junctionResource.resourceId).list([Filters.EQ(resourceColumnNameInJunctionResource, recordId)]);
145+
if (recordId === undefined || recordId === null || recordId === '') {
146+
return { ok: true };
147+
}
148+
const existingJunctionRecords = await this.adminforth.resource(this.junctionResource.resourceId).list([Filters.EQ(this.resourceColumnNameInJunctionResource, recordId)]);
149+
const junctionPkName = this.junctionResource.columns.find(c => c.primaryKey)?.name;
139150
for(const jr of existingJunctionRecords) {
140151
await this.adminforth.deleteResourceRecord({
141-
resource: junctionResource,
142-
recordId: jr[junctionResource.columns.find(c => c.primaryKey).name],
152+
resource: this.junctionResource,
153+
recordId: jr[junctionPkName],
143154
record: jr,
144155
adminUser
145156
});
146157
}
147158
return { ok: true };
148159
});
149160
const linkedResource = this.adminforth.config.resources.find(r => r.resourceId === this.options.linkedResourceId);
161+
if (!linkedResource) {
162+
throw new Error(`Linked resource not found: ${this.options.linkedResourceId}`);
163+
}
150164
linkedResource.hooks.delete.beforeSave.push(async ({ recordId, record, adminUser }: { recordId: any, record: any, adminUser: any }) => {
151-
const existingJunctionRecords = await this.adminforth.resource(junctionResource.resourceId).list([Filters.EQ(linkedColumnNameInJunctionResource, recordId)]);
165+
if (recordId === undefined || recordId === null || recordId === '') {
166+
return { ok: true };
167+
}
168+
const existingJunctionRecords = await this.adminforth.resource(this.junctionResource.resourceId).list([Filters.EQ(this.linkedColumnNameInJunctionResource, recordId)]);
169+
const junctionPkName = this.junctionResource.columns.find(c => c.primaryKey)?.name;
152170
for(const jr of existingJunctionRecords) {
153171
await this.adminforth.deleteResourceRecord({
154-
resource: junctionResource,
155-
recordId: jr[junctionResource.columns.find(c => c.primaryKey).name],
172+
resource: this.junctionResource,
173+
recordId: jr[junctionPkName],
156174
record: jr,
157175
adminUser
158176
});
@@ -181,22 +199,30 @@ export default class extends AdminForthPlugin {
181199
if (recordId === undefined || recordId === null || recordId === '') {
182200
return { ok: true, data: [] };
183201
}
184-
const junctionRecords = await this.adminforth.resource(junctionResource.resourceId).list([Filters.EQ(resourceColumnNameInJunctionResource, recordId)]);
185-
let dataToReturn = [];
186-
const junctionResourcePkColumn = junctionResource.columns.find(c => c.primaryKey);
187-
const linkedResource = this.adminforth.resource(this.options.linkedResourceId);
202+
const junctionRecords = await this.adminforth.resource(this.junctionResource.resourceId).list([Filters.EQ(this.resourceColumnNameInJunctionResource, recordId)]);
203+
const dataToReturn = [];
204+
const linkedResourceConfig = this.adminforth.config.resources.find(r => r.resourceId === this.options.linkedResourceId);
205+
if (!linkedResourceConfig) {
206+
throw new Error(`Linked resource not found: ${this.options.linkedResourceId}`);
207+
}
208+
const linkedPkColumn = linkedResourceConfig.columns.find(c => c.primaryKey);
209+
if (!linkedPkColumn) {
210+
throw new Error(`Linked resource ${this.options.linkedResourceId} has no primary key`);
211+
}
212+
const linkedOperationalResource = this.adminforth.resource(this.options.linkedResourceId);
188213
for(const jr of junctionRecords) {
189-
const record = await this.adminforth.resource(this.options.linkedResourceId).get([Filters.EQ(junctionResourcePkColumn.name, jr[linkedColumnNameInJunctionResource])]);
190-
if ( !returnLabels ) {
191-
dataToReturn.push (record[junctionResourcePkColumn.name]);
214+
const linkedId = jr[this.linkedColumnNameInJunctionResource];
215+
if (!returnLabels) {
216+
dataToReturn.push(linkedId);
192217
} else {
193-
dataToReturn.push (
194-
(linkedResource as any).resourceConfig.recordLabel ? {
195-
label: (linkedResource as any).resourceConfig.recordLabel(record),
196-
value: record[junctionResourcePkColumn.name]
218+
const linkedRecord = await linkedOperationalResource.get([Filters.EQ(linkedPkColumn.name, linkedId)]);
219+
dataToReturn.push(
220+
(linkedOperationalResource as any).resourceConfig.recordLabel ? {
221+
label: (linkedOperationalResource as any).resourceConfig.recordLabel(linkedRecord),
222+
value: linkedRecord[linkedPkColumn.name]
197223
} : {
198-
label: record[junctionResourcePkColumn.name],
199-
value: record[junctionResourcePkColumn.name]
224+
label: linkedRecord[linkedPkColumn.name],
225+
value: linkedRecord[linkedPkColumn.name]
200226
}
201227
);
202228
}

0 commit comments

Comments
 (0)