mirror of
https://github.com/immich-app/immich
synced 2025-10-17 18:19:27 +00:00
feat: partner sharing from specific date
This commit is contained in:
parent
dbee133764
commit
f1390febf3
10 changed files with 5218 additions and 3096 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
import { LoginResponseDto, createPartner } from '@immich/sdk';
|
import { LoginResponseDto, createPartner, getAssetInfo } from '@immich/sdk';
|
||||||
import { createUserDto } from 'src/fixtures';
|
import { createUserDto } from 'src/fixtures';
|
||||||
import { errorDto } from 'src/responses';
|
import { errorDto } from 'src/responses';
|
||||||
import { app, asBearerAuth, utils } from 'src/utils';
|
import { app, asBearerAuth, utils } from 'src/utils';
|
||||||
|
|
@ -101,6 +101,105 @@ describe('/partners', () => {
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toEqual(expect.objectContaining({ id: user2.userId, inTimeline: false }));
|
expect(body).toEqual(expect.objectContaining({ id: user2.userId, inTimeline: false }));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should update partner with startDate', async () => {
|
||||||
|
const startDate = '2024-01-01T00:00:00.000Z';
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/partners/${user2.userId}`)
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
.send({ inTimeline: true, startDate });
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual(expect.objectContaining({ id: user2.userId, inTimeline: true, startDate }));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clear partner startDate when set to null', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/partners/${user2.userId}`)
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
.send({ inTimeline: true, startDate: null });
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual(expect.objectContaining({ id: user2.userId, inTimeline: true }));
|
||||||
|
expect(body.startDate).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('POST /partners with startDate', () => {
|
||||||
|
it('should create partner with startDate', async () => {
|
||||||
|
const startDate = '2024-06-01T00:00:00.000Z';
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.post('/partners')
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
.send({ sharedWithId: user3.userId, startDate });
|
||||||
|
|
||||||
|
expect(status).toBe(201);
|
||||||
|
expect(body).toEqual(expect.objectContaining({ id: user3.userId, startDate }));
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
await request(app).delete(`/partners/${user3.userId}`).set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /partners with startDate', () => {
|
||||||
|
it('should return partner with startDate in response', async () => {
|
||||||
|
const startDate = '2023-12-01T00:00:00.000Z';
|
||||||
|
|
||||||
|
// Create partner with startDate
|
||||||
|
await request(app)
|
||||||
|
.post('/partners')
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
.send({ sharedWithId: user3.userId, startDate });
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/partners')
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
.query({ direction: 'shared-by' });
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
const partner = body.find((p: any) => p.id === user3.userId);
|
||||||
|
expect(partner).toBeDefined();
|
||||||
|
expect(partner.startDate).toBe(startDate);
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
await request(app).delete(`/partners/${user3.userId}`).set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Partner asset access with startDate', () => {
|
||||||
|
it('should filter partner assets by startDate', async () => {
|
||||||
|
// Create assets with different dates for user2
|
||||||
|
const oldAsset = await utils.createAsset(user2.accessToken, {
|
||||||
|
fileCreatedAt: new Date('2023-01-01T00:00:00.000Z').toISOString(),
|
||||||
|
});
|
||||||
|
const newAsset = await utils.createAsset(user2.accessToken, {
|
||||||
|
fileCreatedAt: new Date('2024-06-01T00:00:00.000Z').toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set partner startDate to 2024-01-01
|
||||||
|
const startDate = '2024-01-01T00:00:00.000Z';
|
||||||
|
await request(app)
|
||||||
|
.put(`/partners/${user2.userId}`)
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
.send({ inTimeline: true, startDate });
|
||||||
|
|
||||||
|
// User1 should be able to access the new asset
|
||||||
|
const newAssetInfo = await getAssetInfo(
|
||||||
|
{ id: newAsset.id },
|
||||||
|
{ headers: asBearerAuth(user1.accessToken) },
|
||||||
|
);
|
||||||
|
expect(newAssetInfo.id).toBe(newAsset.id);
|
||||||
|
|
||||||
|
// User1 should NOT be able to access the old asset (before startDate)
|
||||||
|
// Note: Access check happens at permission level, not returning the asset
|
||||||
|
// We verify by checking if it appears in timeline/search results
|
||||||
|
const { status: oldStatus } = await request(app)
|
||||||
|
.get(`/assets/${oldAsset.id}`)
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
|
||||||
|
// The old asset should be denied access (403) or not found (404) due to startDate filter
|
||||||
|
expect([403, 404]).toContain(oldStatus);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('DELETE /partners/:id', () => {
|
describe('DELETE /partners/:id', () => {
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -215,6 +215,7 @@ export type Partner = {
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
updateId: string;
|
updateId: string;
|
||||||
inTimeline: boolean;
|
inTimeline: boolean;
|
||||||
|
startDate: Date | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Place = {
|
export type Place = {
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,22 @@
|
||||||
import { IsNotEmpty } from 'class-validator';
|
import { IsNotEmpty } from 'class-validator';
|
||||||
import { UserResponseDto } from 'src/dtos/user.dto';
|
import { UserResponseDto } from 'src/dtos/user.dto';
|
||||||
import { PartnerDirection } from 'src/repositories/partner.repository';
|
import { PartnerDirection } from 'src/repositories/partner.repository';
|
||||||
import { ValidateEnum, ValidateUUID } from 'src/validation';
|
import { ValidateDate, ValidateEnum, ValidateUUID } from 'src/validation';
|
||||||
|
|
||||||
export class PartnerCreateDto {
|
export class PartnerCreateDto {
|
||||||
@ValidateUUID()
|
@ValidateUUID()
|
||||||
sharedWithId!: string;
|
sharedWithId!: string;
|
||||||
|
|
||||||
|
@ValidateDate({ optional: true, nullable: true, format: 'date-time' })
|
||||||
|
startDate?: Date | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PartnerUpdateDto {
|
export class PartnerUpdateDto {
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
inTimeline!: boolean;
|
inTimeline!: boolean;
|
||||||
|
|
||||||
|
@ValidateDate({ optional: true, nullable: true, format: 'date-time' })
|
||||||
|
startDate?: Date | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PartnerSearchDto {
|
export class PartnerSearchDto {
|
||||||
|
|
@ -20,4 +26,5 @@ export class PartnerSearchDto {
|
||||||
|
|
||||||
export class PartnerResponseDto extends UserResponseDto {
|
export class PartnerResponseDto extends UserResponseDto {
|
||||||
inTimeline?: boolean;
|
inTimeline?: boolean;
|
||||||
|
startDate?: Date | null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -207,7 +207,14 @@ class AssetAccess {
|
||||||
eb('asset.visibility', '=', sql.lit(AssetVisibility.Hidden)),
|
eb('asset.visibility', '=', sql.lit(AssetVisibility.Hidden)),
|
||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
|
.$if(true, (qb) =>
|
||||||
|
qb.where((eb) =>
|
||||||
|
eb.or([
|
||||||
|
eb('partner.startDate', 'is', null),
|
||||||
|
eb('asset.localDateTime', '>=', eb.ref('partner.startDate')),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
)
|
||||||
.where('asset.id', 'in', [...assetIds])
|
.where('asset.id', 'in', [...assetIds])
|
||||||
.execute()
|
.execute()
|
||||||
.then((assets) => new Set(assets.map((asset) => asset.id)));
|
.then((assets) => new Set(assets.map((asset) => asset.id)));
|
||||||
|
|
|
||||||
|
|
@ -542,6 +542,25 @@ export class AssetRepository {
|
||||||
.$call(withExif)
|
.$call(withExif)
|
||||||
.$call(withDefaultVisibility)
|
.$call(withDefaultVisibility)
|
||||||
.where('ownerId', '=', anyUuid(userIds))
|
.where('ownerId', '=', anyUuid(userIds))
|
||||||
|
.$if(userIds.length > 1, (qb) =>
|
||||||
|
qb
|
||||||
|
.leftJoin('partner', (join) =>
|
||||||
|
join
|
||||||
|
.onRef('partner.sharedById', '=', 'asset.ownerId')
|
||||||
|
.on((eb) =>
|
||||||
|
eb.or(
|
||||||
|
userIds.map((uid) => eb('partner.sharedWithId', '=', uid))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.where((eb) =>
|
||||||
|
eb.or([
|
||||||
|
eb('partner.startDate', 'is', null),
|
||||||
|
eb('partner.sharedById', 'is', null),
|
||||||
|
eb('asset.localDateTime', '>=', eb.ref('partner.startDate')),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
)
|
||||||
.where('deletedAt', 'is', null)
|
.where('deletedAt', 'is', null)
|
||||||
.orderBy((eb) => eb.fn('random'))
|
.orderBy((eb) => eb.fn('random'))
|
||||||
.limit(take)
|
.limit(take)
|
||||||
|
|
@ -572,7 +591,29 @@ export class AssetRepository {
|
||||||
)
|
)
|
||||||
.where((eb) => eb.or([eb('asset.stackId', 'is', null), eb(eb.table('stack'), 'is not', null)])),
|
.where((eb) => eb.or([eb('asset.stackId', 'is', null), eb(eb.table('stack'), 'is not', null)])),
|
||||||
)
|
)
|
||||||
.$if(!!options.userIds, (qb) => qb.where('asset.ownerId', '=', anyUuid(options.userIds!)))
|
.$if(!!options.userIds, (qb) =>
|
||||||
|
qb
|
||||||
|
.where('asset.ownerId', '=', anyUuid(options.userIds!))
|
||||||
|
.$if(options.userIds!.length > 1, (qb2) =>
|
||||||
|
qb2
|
||||||
|
.leftJoin('partner', (join) =>
|
||||||
|
join
|
||||||
|
.onRef('partner.sharedById', '=', 'asset.ownerId')
|
||||||
|
.on((eb) =>
|
||||||
|
eb.or(
|
||||||
|
options.userIds!.map((uid) => eb('partner.sharedWithId', '=', uid))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.where((eb) =>
|
||||||
|
eb.or([
|
||||||
|
eb('partner.startDate', 'is', null),
|
||||||
|
eb('partner.sharedById', 'is', null),
|
||||||
|
eb('asset.localDateTime', '>=', eb.ref('partner.startDate')),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
.$if(options.isFavorite !== undefined, (qb) => qb.where('asset.isFavorite', '=', options.isFavorite!))
|
.$if(options.isFavorite !== undefined, (qb) => qb.where('asset.isFavorite', '=', options.isFavorite!))
|
||||||
.$if(!!options.assetType, (qb) => qb.where('asset.type', '=', options.assetType!))
|
.$if(!!options.assetType, (qb) => qb.where('asset.type', '=', options.assetType!))
|
||||||
.$if(options.isDuplicate !== undefined, (qb) =>
|
.$if(options.isDuplicate !== undefined, (qb) =>
|
||||||
|
|
@ -645,7 +686,29 @@ export class AssetRepository {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.$if(!!options.personId, (qb) => hasPeople(qb, [options.personId!]))
|
.$if(!!options.personId, (qb) => hasPeople(qb, [options.personId!]))
|
||||||
.$if(!!options.userIds, (qb) => qb.where('asset.ownerId', '=', anyUuid(options.userIds!)))
|
.$if(!!options.userIds, (qb) =>
|
||||||
|
qb
|
||||||
|
.where('asset.ownerId', '=', anyUuid(options.userIds!))
|
||||||
|
.$if(options.userIds!.length > 1, (qb2) =>
|
||||||
|
qb2
|
||||||
|
.leftJoin('partner', (join) =>
|
||||||
|
join
|
||||||
|
.onRef('partner.sharedById', '=', 'asset.ownerId')
|
||||||
|
.on((eb) =>
|
||||||
|
eb.or(
|
||||||
|
options.userIds!.map((uid) => eb('partner.sharedWithId', '=', uid))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.where((eb) =>
|
||||||
|
eb.or([
|
||||||
|
eb('partner.startDate', 'is', null),
|
||||||
|
eb('partner.sharedById', 'is', null),
|
||||||
|
eb('asset.localDateTime', '>=', eb.ref('partner.startDate')),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
.$if(options.isFavorite !== undefined, (qb) => qb.where('asset.isFavorite', '=', options.isFavorite!))
|
.$if(options.isFavorite !== undefined, (qb) => qb.where('asset.isFavorite', '=', options.isFavorite!))
|
||||||
.$if(!!options.withStacked, (qb) =>
|
.$if(!!options.withStacked, (qb) =>
|
||||||
qb
|
qb
|
||||||
|
|
@ -804,6 +867,25 @@ export class AssetRepository {
|
||||||
)
|
)
|
||||||
.select((eb) => eb.fn.toJson(eb.table('stacked_assets').$castTo<Stack | null>()).as('stack'))
|
.select((eb) => eb.fn.toJson(eb.table('stacked_assets').$castTo<Stack | null>()).as('stack'))
|
||||||
.where('asset.ownerId', '=', anyUuid(options.userIds))
|
.where('asset.ownerId', '=', anyUuid(options.userIds))
|
||||||
|
.$if(options.userIds.length > 1, (qb) =>
|
||||||
|
qb
|
||||||
|
.leftJoin('partner', (join) =>
|
||||||
|
join
|
||||||
|
.onRef('partner.sharedById', '=', 'asset.ownerId')
|
||||||
|
.on((eb) =>
|
||||||
|
eb.or(
|
||||||
|
options.userIds.map((uid) => eb('partner.sharedWithId', '=', uid))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.where((eb) =>
|
||||||
|
eb.or([
|
||||||
|
eb('partner.startDate', 'is', null),
|
||||||
|
eb('partner.sharedById', 'is', null),
|
||||||
|
eb('asset.localDateTime', '>=', eb.ref('partner.startDate')),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
)
|
||||||
.where('asset.visibility', '!=', AssetVisibility.Hidden)
|
.where('asset.visibility', '!=', AssetVisibility.Hidden)
|
||||||
.where('asset.updatedAt', '>', options.updatedAfter)
|
.where('asset.updatedAt', '>', options.updatedAfter)
|
||||||
.limit(options.limit)
|
.limit(options.limit)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { Kysely, sql } from 'kysely';
|
||||||
|
|
||||||
|
export async function up(db: Kysely<any>): Promise<void> {
|
||||||
|
await sql`ALTER TABLE "partner" ADD "startDate" timestamp with time zone;`.execute(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(db: Kysely<any>): Promise<void> {
|
||||||
|
await sql`ALTER TABLE "partner" DROP COLUMN "startDate";`.execute(db);
|
||||||
|
}
|
||||||
|
|
@ -44,6 +44,9 @@ export class PartnerTable {
|
||||||
@Column({ type: 'boolean', default: false })
|
@Column({ type: 'boolean', default: false })
|
||||||
inTimeline!: Generated<boolean>;
|
inTimeline!: Generated<boolean>;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||||
|
startDate!: Timestamp | null;
|
||||||
|
|
||||||
@UpdateIdColumn({ index: true })
|
@UpdateIdColumn({ index: true })
|
||||||
updateId!: Generated<string>;
|
updateId!: Generated<string>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,25 @@ describe(PartnerService.name, () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should create a new partner with startDate', async () => {
|
||||||
|
const user1 = factory.user();
|
||||||
|
const user2 = factory.user();
|
||||||
|
const startDate = new Date('2024-01-01T00:00:00.000Z');
|
||||||
|
const partner = factory.partner({ sharedBy: user1, sharedWith: user2, startDate });
|
||||||
|
const auth = factory.auth({ user: { id: user1.id } });
|
||||||
|
|
||||||
|
mocks.partner.get.mockResolvedValue(void 0);
|
||||||
|
mocks.partner.create.mockResolvedValue(partner);
|
||||||
|
|
||||||
|
await expect(sut.create(auth, { sharedWithId: user2.id, startDate })).resolves.toBeDefined();
|
||||||
|
|
||||||
|
expect(mocks.partner.create).toHaveBeenCalledWith({
|
||||||
|
sharedById: partner.sharedById,
|
||||||
|
sharedWithId: partner.sharedWithId,
|
||||||
|
startDate,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should throw an error when the partner already exists', async () => {
|
it('should throw an error when the partner already exists', async () => {
|
||||||
const user1 = factory.user();
|
const user1 = factory.user();
|
||||||
const user2 = factory.user();
|
const user2 = factory.user();
|
||||||
|
|
@ -124,5 +143,22 @@ describe(PartnerService.name, () => {
|
||||||
{ inTimeline: true },
|
{ inTimeline: true },
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should update partner with startDate', async () => {
|
||||||
|
const user1 = factory.user();
|
||||||
|
const user2 = factory.user();
|
||||||
|
const startDate = new Date('2024-01-01T00:00:00.000Z');
|
||||||
|
const partner = factory.partner({ sharedBy: user1, sharedWith: user2, startDate });
|
||||||
|
const auth = factory.auth({ user: { id: user1.id } });
|
||||||
|
|
||||||
|
mocks.access.partner.checkUpdateAccess.mockResolvedValue(new Set([user2.id]));
|
||||||
|
mocks.partner.update.mockResolvedValue(partner);
|
||||||
|
|
||||||
|
await expect(sut.update(auth, user2.id, { inTimeline: true, startDate })).resolves.toBeDefined();
|
||||||
|
expect(mocks.partner.update).toHaveBeenCalledWith(
|
||||||
|
{ sharedById: user2.id, sharedWithId: user1.id },
|
||||||
|
{ inTimeline: true, startDate },
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,14 @@ import { BaseService } from 'src/services/base.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PartnerService extends BaseService {
|
export class PartnerService extends BaseService {
|
||||||
async create(auth: AuthDto, { sharedWithId }: PartnerCreateDto): Promise<PartnerResponseDto> {
|
async create(auth: AuthDto, { sharedWithId, startDate }: PartnerCreateDto): Promise<PartnerResponseDto> {
|
||||||
const partnerId: PartnerIds = { sharedById: auth.user.id, sharedWithId };
|
const partnerId: PartnerIds = { sharedById: auth.user.id, sharedWithId };
|
||||||
const exists = await this.partnerRepository.get(partnerId);
|
const exists = await this.partnerRepository.get(partnerId);
|
||||||
if (exists) {
|
if (exists) {
|
||||||
throw new BadRequestException(`Partner already exists`);
|
throw new BadRequestException(`Partner already exists`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const partner = await this.partnerRepository.create(partnerId);
|
const partner = await this.partnerRepository.create({ ...partnerId, startDate: startDate || null });
|
||||||
return this.mapPartner(partner, PartnerDirection.SharedBy);
|
return this.mapPartner(partner, PartnerDirection.SharedBy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,7 +43,12 @@ export class PartnerService extends BaseService {
|
||||||
await this.requireAccess({ auth, permission: Permission.PartnerUpdate, ids: [sharedById] });
|
await this.requireAccess({ auth, permission: Permission.PartnerUpdate, ids: [sharedById] });
|
||||||
const partnerId: PartnerIds = { sharedById, sharedWithId: auth.user.id };
|
const partnerId: PartnerIds = { sharedById, sharedWithId: auth.user.id };
|
||||||
|
|
||||||
const entity = await this.partnerRepository.update(partnerId, { inTimeline: dto.inTimeline });
|
const updateData: { inTimeline: boolean; startDate?: Date | null } = { inTimeline: dto.inTimeline };
|
||||||
|
if (dto.startDate !== undefined) {
|
||||||
|
updateData.startDate = dto.startDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entity = await this.partnerRepository.update(partnerId, updateData);
|
||||||
return this.mapPartner(entity, PartnerDirection.SharedWith);
|
return this.mapPartner(entity, PartnerDirection.SharedWith);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,6 +58,6 @@ export class PartnerService extends BaseService {
|
||||||
direction === PartnerDirection.SharedBy ? partner.sharedWith : partner.sharedBy,
|
direction === PartnerDirection.SharedBy ? partner.sharedWith : partner.sharedBy,
|
||||||
) as PartnerResponseDto;
|
) as PartnerResponseDto;
|
||||||
|
|
||||||
return { ...user, inTimeline: partner.inTimeline };
|
return { ...user, inTimeline: partner.inTimeline, startDate: partner.startDate };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue