@@ -17,6 +17,25 @@ import { chartConfig } from './config/chart';
1717import { sheetFormConfig } from './config/sheet-form-view' ;
1818import type { IOfficialPluginConfig } from './config/types' ;
1919
20+ interface IUploadResult {
21+ id : string ;
22+ path : string ;
23+ url : string ;
24+ size : number ;
25+ width ?: number ;
26+ height ?: number ;
27+ hash : string ;
28+ mimetype : string ;
29+ }
30+
31+ interface IPreparedPlugin {
32+ config : IOfficialPluginConfig & { secret : string ; url : string } ;
33+ logo : IUploadResult ;
34+ avatar ?: IUploadResult ;
35+ hashedSecret : string ;
36+ maskedSecret : string ;
37+ }
38+
2039@Injectable ( )
2140export class OfficialPluginInitService implements OnModuleInit {
2241 private logger = new Logger ( OfficialPluginInitService . name ) ;
@@ -30,7 +49,6 @@ export class OfficialPluginInitService implements OnModuleInit {
3049 @InjectModel ( 'CUSTOM_KNEX' ) private readonly knex : Knex
3150 ) { }
3251
33- // init official plugins
3452 async onModuleInit ( ) {
3553 const officialPlugins = [
3654 {
@@ -48,10 +66,18 @@ export class OfficialPluginInitService implements OnModuleInit {
4866 ] ;
4967
5068 try {
69+ // Phase 1: Upload files to storage (outside transaction)
70+ const preparedPlugins : IPreparedPlugin [ ] = [ ] ;
71+ for ( const plugin of officialPlugins ) {
72+ this . logger . log ( `Creating official plugin: ${ plugin . name } ` ) ;
73+ const prepared = await this . preparePlugin ( plugin ) ;
74+ preparedPlugins . push ( prepared ) ;
75+ }
76+
77+ // Phase 2: Database operations (inside transaction)
5178 await this . prismaService . $tx ( async ( ) => {
52- for ( const plugin of officialPlugins ) {
53- this . logger . log ( `Creating official plugin: ${ plugin . name } ` ) ;
54- await this . createOfficialPlugin ( plugin ) ;
79+ for ( const prepared of preparedPlugins ) {
80+ await this . savePlugin ( prepared ) ;
5581 }
5682 } ) ;
5783 // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -63,21 +89,33 @@ export class OfficialPluginInitService implements OnModuleInit {
6389 this . logger . log ( 'Official plugins initialized' ) ;
6490 }
6591
66- async uploadStatic ( id : string , filePath : string , type : UploadType ) {
92+ private async uploadToStorage (
93+ id : string ,
94+ filePath : string ,
95+ type : UploadType
96+ ) : Promise < IUploadResult > {
97+ const path = join ( StorageAdapter . getDir ( type ) , id ) ;
98+
6799 if ( process . env . NODE_ENV === 'test' ) {
68- return `/${ join ( StorageAdapter . getDir ( type ) , id ) } ` ;
100+ return { id , path , url : `/${ path } ` , size : 0 , hash : '' , mimetype : 'image/png' } ;
69101 }
102+
70103 const fileStream = createReadStream ( resolve ( process . cwd ( ) , filePath ) ) ;
71104 const metaReader = sharp ( ) ;
72105 const sharpReader = fileStream . pipe ( metaReader ) ;
73106 const { width, height, format = 'png' , size = 0 } = await sharpReader . metadata ( ) ;
74- const path = join ( StorageAdapter . getDir ( type ) , id ) ;
75107 const bucket = StorageAdapter . getBucket ( type ) ;
76108 const mimetype = `image/${ format } ` ;
77109 const { hash } = await this . storageAdapter . uploadFileWidthPath ( bucket , path , filePath , {
78110 // eslint-disable-next-line @typescript-eslint/naming-convention
79111 'Content-Type' : mimetype ,
80112 } ) ;
113+
114+ return { id, path, url : `/${ path } ` , size, width, height, hash, mimetype } ;
115+ }
116+
117+ private async saveAttachment ( upload : IUploadResult ) : Promise < void > {
118+ const { id, path, size, width, height, hash, mimetype } = upload ;
81119 await this . prismaService . txClient ( ) . attachments . upsert ( {
82120 create : {
83121 token : id ,
@@ -102,106 +140,91 @@ export class OfficialPluginInitService implements OnModuleInit {
102140 deletedTime : null ,
103141 } ,
104142 } ) ;
105- return `/${ path } ` ;
106143 }
107144
108- async createOfficialPlugin (
145+ private async preparePlugin (
109146 pluginConfig : IOfficialPluginConfig & { secret : string ; url : string }
110- ) {
147+ ) : Promise < IPreparedPlugin > {
148+ const { id : pluginId , logoPath, avatarPath, pluginUserId, secret } = pluginConfig ;
149+
150+ const logo = await this . uploadToStorage ( pluginId , logoPath , UploadType . Plugin ) ;
151+ const { hashedSecret, maskedSecret } = await generateSecret ( secret ) ;
152+
153+ let avatar : IUploadResult | undefined ;
154+ if ( pluginUserId && avatarPath ) {
155+ avatar = await this . uploadToStorage ( pluginUserId , avatarPath , UploadType . Avatar ) ;
156+ }
157+
158+ return { config : pluginConfig , logo, avatar, hashedSecret, maskedSecret } ;
159+ }
160+
161+ private async savePlugin ( prepared : IPreparedPlugin ) : Promise < void > {
162+ const { config, logo, avatar, hashedSecret, maskedSecret } = prepared ;
111163 const {
112164 id : pluginId ,
113165 name,
114166 description,
115167 detailDesc,
116- logoPath,
117168 i18n,
118169 positions,
119170 helpUrl,
120- secret,
121171 url,
122172 pluginUserId,
123- avatarPath,
124- } = pluginConfig ;
173+ } = config ;
125174
126- const rows = await this . prismaService . txClient ( ) . plugin . count ( { where : { id : pluginId } } ) ;
127- // upload logo
128- const logo = await this . uploadStatic ( pluginId , logoPath , UploadType . Plugin ) ;
129- const { hashedSecret, maskedSecret } = await generateSecret ( secret ) ;
175+ // Save attachments
176+ await this . saveAttachment ( logo ) ;
177+ if ( avatar ) {
178+ await this . saveAttachment ( avatar ) ;
179+ }
180+
181+ // Create plugin user if needed
130182 let userId : string | undefined ;
131183 if ( pluginUserId ) {
132184 const userEmail = getPluginEmail ( pluginId ) ;
133- // create plugin user
134185 const user = await this . prismaService
135186 . txClient ( )
136187 . user . findFirst ( { where : { id : pluginUserId , email : userEmail } } ) ;
137- let avatar : string | undefined ;
138- if ( avatarPath ) {
139- // upload user avatar
140- avatar = await this . uploadStatic ( pluginUserId , avatarPath , UploadType . Avatar ) ;
141- }
188+
142189 if ( ! user ) {
143190 await this . userService . createSystemUser ( {
144191 id : pluginUserId ,
145192 name,
146- avatar,
193+ avatar : avatar ?. url ,
147194 email : userEmail ,
148195 } ) ;
149196 }
150197 userId = pluginUserId ;
151198 }
152- if ( rows > 0 ) {
153- return this . prismaService . txClient ( ) . plugin . update ( {
154- where : {
155- id : pluginId ,
156- } ,
157- data : {
158- name,
159- description,
160- detailDesc,
161- positions : JSON . stringify ( positions ) ,
162- helpUrl,
163- url,
164- logo,
165- status : PluginStatus . Published ,
166- i18n : JSON . stringify ( i18n ) ,
167- secret : hashedSecret ,
168- maskedSecret,
169- pluginUser : userId || pluginUserId ,
170- createdBy : 'system' ,
171- } ,
199+
200+ // Create or update plugin
201+ const pluginData = {
202+ name,
203+ description,
204+ detailDesc,
205+ positions : JSON . stringify ( positions ) ,
206+ helpUrl,
207+ url,
208+ logo : logo . url ,
209+ status : PluginStatus . Published ,
210+ i18n : JSON . stringify ( i18n ) ,
211+ secret : hashedSecret ,
212+ maskedSecret,
213+ pluginUser : userId || pluginUserId ,
214+ createdBy : 'system' ,
215+ } ;
216+
217+ const exists = await this . prismaService . txClient ( ) . plugin . count ( { where : { id : pluginId } } ) ;
218+
219+ if ( exists > 0 ) {
220+ await this . prismaService . txClient ( ) . plugin . update ( {
221+ where : { id : pluginId } ,
222+ data : pluginData ,
223+ } ) ;
224+ } else {
225+ await this . prismaService . txClient ( ) . plugin . create ( {
226+ data : { id : pluginId , ...pluginData } ,
172227 } ) ;
173228 }
174- return this . prismaService . txClient ( ) . plugin . create ( {
175- select : {
176- id : true ,
177- name : true ,
178- description : true ,
179- detailDesc : true ,
180- positions : true ,
181- helpUrl : true ,
182- logo : true ,
183- url : true ,
184- status : true ,
185- i18n : true ,
186- secret : true ,
187- createdTime : true ,
188- } ,
189- data : {
190- id : pluginId ,
191- name,
192- description,
193- detailDesc,
194- positions : JSON . stringify ( positions ) ,
195- helpUrl,
196- url,
197- logo,
198- status : PluginStatus . Published ,
199- i18n : JSON . stringify ( i18n ) ,
200- secret : hashedSecret ,
201- maskedSecret,
202- pluginUser : userId || pluginUserId ,
203- createdBy : 'system' ,
204- } ,
205- } ) ;
206229 }
207230}
0 commit comments