feat(sync): remove target avatar when source has none
Add removeProjectAvatar and removeGroupAvatar methods to keep avatars fully in sync by clearing the target avatar when the source has none.
This commit is contained in:
@@ -1139,6 +1139,7 @@ export class SyncManager {
|
||||
} else if (config.useGroupAvatarsForProjects) {
|
||||
// Project has no avatar — inherit from parent group
|
||||
const groupPath = sourceFullPath.substring(0, sourceFullPath.lastIndexOf('/'));
|
||||
let groupAvatarApplied = false;
|
||||
if (groupPath) {
|
||||
try {
|
||||
const sourceGroup = await this.fetchGroupRaw(sourceConn, groupPath);
|
||||
@@ -1147,6 +1148,7 @@ export class SyncManager {
|
||||
if (groupMeta.avatarUrl) {
|
||||
logger.syncLog('info', `Applying group avatar to ${targetFullPath}`, 'api');
|
||||
await this.syncProjectAvatar(sourceConn, targetConn, sourceFullPath, targetFullPath, groupMeta.avatarUrl, targetProject);
|
||||
groupAvatarApplied = true;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -1154,6 +1156,15 @@ export class SyncManager {
|
||||
logger.syncLog('warn', `Group avatar sync failed for ${targetFullPath}: ${errMsg}`, 'api');
|
||||
}
|
||||
}
|
||||
// If group also has no avatar, remove target avatar
|
||||
if (!groupAvatarApplied && targetMeta.avatarUrl) {
|
||||
await this.removeProjectAvatar(targetConn, targetFullPath, targetProject);
|
||||
}
|
||||
} else {
|
||||
// No source avatar, no group fallback — remove target avatar if present
|
||||
if (targetMeta.avatarUrl) {
|
||||
await this.removeProjectAvatar(targetConn, targetFullPath, targetProject);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
const errMsg = err instanceof Error ? err.message : String(err);
|
||||
@@ -1199,6 +1210,8 @@ export class SyncManager {
|
||||
// Sync avatar
|
||||
if (sourceMeta.avatarUrl) {
|
||||
await this.syncGroupAvatar(sourceConn, targetConn, sourceGroupPath, targetGroupPath, sourceMeta.avatarUrl, targetGroup);
|
||||
} else if (targetMeta.avatarUrl) {
|
||||
await this.removeGroupAvatar(targetConn, targetGroupPath, targetGroup);
|
||||
}
|
||||
} catch (err) {
|
||||
const errMsg = err instanceof Error ? err.message : String(err);
|
||||
@@ -1383,6 +1396,25 @@ export class SyncManager {
|
||||
}
|
||||
}
|
||||
|
||||
private async removeProjectAvatar(
|
||||
targetConn: interfaces.data.IProviderConnection,
|
||||
targetFullPath: string,
|
||||
targetRawProject: any,
|
||||
): Promise<void> {
|
||||
logger.syncLog('info', `Removing avatar from ${targetFullPath}`, 'api');
|
||||
if (targetConn.providerType === 'gitlab') {
|
||||
await this.rawApiCall(targetConn, 'PUT', `/api/v4/projects/${targetRawProject.id}`, {
|
||||
avatar: '',
|
||||
});
|
||||
} else {
|
||||
const segments = targetFullPath.split('/');
|
||||
const repo = segments.pop()!;
|
||||
const owner = segments[0] || '';
|
||||
await this.rawApiCall(targetConn, 'DELETE',
|
||||
`/api/v1/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/avatar`);
|
||||
}
|
||||
}
|
||||
|
||||
private async syncGroupAvatar(
|
||||
sourceConn: interfaces.data.IProviderConnection,
|
||||
targetConn: interfaces.data.IProviderConnection,
|
||||
@@ -1422,6 +1454,23 @@ export class SyncManager {
|
||||
}
|
||||
}
|
||||
|
||||
private async removeGroupAvatar(
|
||||
targetConn: interfaces.data.IProviderConnection,
|
||||
targetGroupPath: string,
|
||||
targetRawGroup: any,
|
||||
): Promise<void> {
|
||||
logger.syncLog('info', `Removing avatar from group ${targetGroupPath}`, 'api');
|
||||
if (targetConn.providerType === 'gitlab') {
|
||||
await this.rawApiCall(targetConn, 'PUT', `/api/v4/groups/${targetRawGroup.id}`, {
|
||||
avatar: '',
|
||||
});
|
||||
} else {
|
||||
const orgName = targetGroupPath.split('/')[0] || targetGroupPath;
|
||||
await this.rawApiCall(targetConn, 'DELETE',
|
||||
`/api/v1/orgs/${encodeURIComponent(orgName)}/avatar`);
|
||||
}
|
||||
}
|
||||
|
||||
private uint8ArrayToBase64(bytes: Uint8Array): string {
|
||||
let binary = '';
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
|
||||
Reference in New Issue
Block a user