mirror of
https://github.com/immich-app/immich.git
synced 2024-12-26 10:50:29 +02:00
feat(server): sql access checks (#6635)
This commit is contained in:
parent
bd87eb309c
commit
7fc4abba72
@ -4,14 +4,17 @@ export const GENERATE_SQL_KEY = 'generate-sql-key';
|
||||
|
||||
export interface GenerateSqlQueries {
|
||||
name?: string;
|
||||
params?: any[];
|
||||
params: unknown[];
|
||||
}
|
||||
|
||||
/** Decorator to enable versioning/tracking of generated Sql */
|
||||
export const GenerateSql = (...options: GenerateSqlQueries[]) => SetMetadata(GENERATE_SQL_KEY, options);
|
||||
|
||||
const UUID = '00000000-0000-4000-a000-000000000000';
|
||||
|
||||
export const DummyValue = {
|
||||
UUID: '00000000-0000-4000-a000-000000000000',
|
||||
UUID,
|
||||
UUID_SET: new Set([UUID]),
|
||||
PAGINATION: { take: 10, skip: 0 },
|
||||
EMAIL: 'user@immich.app',
|
||||
STRING: 'abcdefghi',
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { IAccessRepository } from '@app/domain';
|
||||
import { IAccessRepository, chunks, setUnion } from '@app/domain';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Brackets, In, Repository } from 'typeorm';
|
||||
import { chunks, setUnion } from '../../domain/domain.util';
|
||||
import {
|
||||
ActivityEntity,
|
||||
AlbumEntity,
|
||||
@ -13,426 +12,503 @@ import {
|
||||
SharedLinkEntity,
|
||||
UserTokenEntity,
|
||||
} from '../entities';
|
||||
import { DATABASE_PARAMETER_CHUNK_SIZE } from '../infra.util';
|
||||
import { DATABASE_PARAMETER_CHUNK_SIZE, DummyValue, GenerateSql } from '../infra.util';
|
||||
|
||||
export class AccessRepository implements IAccessRepository {
|
||||
type IActivityAccess = IAccessRepository['activity'];
|
||||
type IAlbumAccess = IAccessRepository['album'];
|
||||
type IAssetAccess = IAccessRepository['asset'];
|
||||
type IAuthDeviceAccess = IAccessRepository['authDevice'];
|
||||
type ILibraryAccess = IAccessRepository['library'];
|
||||
type ITimelineAccess = IAccessRepository['timeline'];
|
||||
type IPersonAccess = IAccessRepository['person'];
|
||||
type IPartnerAccess = IAccessRepository['partner'];
|
||||
|
||||
class ActivityAccess implements IActivityAccess {
|
||||
constructor(
|
||||
@InjectRepository(ActivityEntity) private activityRepository: Repository<ActivityEntity>,
|
||||
@InjectRepository(AssetEntity) private assetRepository: Repository<AssetEntity>,
|
||||
@InjectRepository(AlbumEntity) private albumRepository: Repository<AlbumEntity>,
|
||||
@InjectRepository(LibraryEntity) private libraryRepository: Repository<LibraryEntity>,
|
||||
@InjectRepository(PartnerEntity) private partnerRepository: Repository<PartnerEntity>,
|
||||
@InjectRepository(PersonEntity) private personRepository: Repository<PersonEntity>,
|
||||
@InjectRepository(AssetFaceEntity) private assetFaceRepository: Repository<AssetFaceEntity>,
|
||||
@InjectRepository(SharedLinkEntity) private sharedLinkRepository: Repository<SharedLinkEntity>,
|
||||
@InjectRepository(UserTokenEntity) private tokenRepository: Repository<UserTokenEntity>,
|
||||
private activityRepository: Repository<ActivityEntity>,
|
||||
private albumRepository: Repository<AlbumEntity>,
|
||||
) {}
|
||||
|
||||
activity = {
|
||||
checkOwnerAccess: async (userId: string, activityIds: Set<string>): Promise<Set<string>> => {
|
||||
if (activityIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
async checkOwnerAccess(userId: string, activityIds: Set<string>): Promise<Set<string>> {
|
||||
if (activityIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(activityIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.activityRepository
|
||||
.find({
|
||||
select: { id: true },
|
||||
where: {
|
||||
id: In(idChunk),
|
||||
userId,
|
||||
},
|
||||
})
|
||||
.then((activities) => new Set(activities.map((activity) => activity.id))),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
},
|
||||
return Promise.all(
|
||||
chunks(activityIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.activityRepository
|
||||
.find({
|
||||
select: { id: true },
|
||||
where: {
|
||||
id: In(idChunk),
|
||||
userId,
|
||||
},
|
||||
})
|
||||
.then((activities) => new Set(activities.map((activity) => activity.id))),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
}
|
||||
|
||||
checkAlbumOwnerAccess: async (userId: string, activityIds: Set<string>): Promise<Set<string>> => {
|
||||
if (activityIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
async checkAlbumOwnerAccess(userId: string, activityIds: Set<string>): Promise<Set<string>> {
|
||||
if (activityIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(activityIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.activityRepository
|
||||
.find({
|
||||
select: { id: true },
|
||||
where: {
|
||||
id: In(idChunk),
|
||||
album: {
|
||||
ownerId: userId,
|
||||
},
|
||||
},
|
||||
})
|
||||
.then((activities) => new Set(activities.map((activity) => activity.id))),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
},
|
||||
|
||||
checkCreateAccess: async (userId: string, albumIds: Set<string>): Promise<Set<string>> => {
|
||||
if (albumIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(albumIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.albumRepository
|
||||
.createQueryBuilder('album')
|
||||
.select('album.id')
|
||||
.leftJoin('album.sharedUsers', 'sharedUsers')
|
||||
.where('album.id IN (:...albumIds)', { albumIds: idChunk })
|
||||
.andWhere('album.isActivityEnabled = true')
|
||||
.andWhere(
|
||||
new Brackets((qb) => {
|
||||
qb.where('album.ownerId = :userId', { userId }).orWhere('sharedUsers.id = :userId', { userId });
|
||||
}),
|
||||
)
|
||||
.getMany()
|
||||
.then((albums) => new Set(albums.map((album) => album.id))),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
},
|
||||
};
|
||||
|
||||
library = {
|
||||
checkOwnerAccess: async (userId: string, libraryIds: Set<string>): Promise<Set<string>> => {
|
||||
if (libraryIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(libraryIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.libraryRepository
|
||||
.find({
|
||||
select: { id: true },
|
||||
where: {
|
||||
id: In(idChunk),
|
||||
return Promise.all(
|
||||
chunks(activityIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.activityRepository
|
||||
.find({
|
||||
select: { id: true },
|
||||
where: {
|
||||
id: In(idChunk),
|
||||
album: {
|
||||
ownerId: userId,
|
||||
},
|
||||
})
|
||||
.then((libraries) => new Set(libraries.map((library) => library.id))),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
},
|
||||
},
|
||||
})
|
||||
.then((activities) => new Set(activities.map((activity) => activity.id))),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
}
|
||||
|
||||
checkPartnerAccess: async (userId: string, partnerIds: Set<string>): Promise<Set<string>> => {
|
||||
if (partnerIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
async checkCreateAccess(userId: string, albumIds: Set<string>): Promise<Set<string>> {
|
||||
if (albumIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(partnerIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.partnerRepository
|
||||
.createQueryBuilder('partner')
|
||||
.select('partner.sharedById')
|
||||
.where('partner.sharedById IN (:...partnerIds)', { partnerIds: idChunk })
|
||||
.andWhere('partner.sharedWithId = :userId', { userId })
|
||||
.getMany()
|
||||
.then((partners) => new Set(partners.map((partner) => partner.sharedById))),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
},
|
||||
};
|
||||
|
||||
timeline = {
|
||||
checkPartnerAccess: async (userId: string, partnerIds: Set<string>): Promise<Set<string>> => {
|
||||
if (partnerIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(partnerIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.partnerRepository
|
||||
.createQueryBuilder('partner')
|
||||
.select('partner.sharedById')
|
||||
.where('partner.sharedById IN (:...partnerIds)', { partnerIds: idChunk })
|
||||
.andWhere('partner.sharedWithId = :userId', { userId })
|
||||
.getMany()
|
||||
.then((partners) => new Set(partners.map((partner) => partner.sharedById))),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
},
|
||||
};
|
||||
|
||||
asset = {
|
||||
checkAlbumAccess: async (userId: string, assetIds: Set<string>): Promise<Set<string>> => {
|
||||
if (assetIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(assetIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.albumRepository
|
||||
.createQueryBuilder('album')
|
||||
.innerJoin('album.assets', 'asset')
|
||||
.leftJoin('album.sharedUsers', 'sharedUsers')
|
||||
.select('asset.id', 'assetId')
|
||||
.addSelect('asset.livePhotoVideoId', 'livePhotoVideoId')
|
||||
.where('array["asset"."id", "asset"."livePhotoVideoId"] && array[:...assetIds]::uuid[]', {
|
||||
assetIds: idChunk,
|
||||
})
|
||||
.andWhere(
|
||||
new Brackets((qb) => {
|
||||
qb.where('album.ownerId = :userId', { userId }).orWhere('sharedUsers.id = :userId', { userId });
|
||||
}),
|
||||
)
|
||||
.getRawMany()
|
||||
.then((rows) => {
|
||||
const allowedIds = new Set<string>();
|
||||
for (const row of rows) {
|
||||
if (row.assetId && assetIds.has(row.assetId)) {
|
||||
allowedIds.add(row.assetId);
|
||||
}
|
||||
if (row.livePhotoVideoId && assetIds.has(row.livePhotoVideoId)) {
|
||||
allowedIds.add(row.livePhotoVideoId);
|
||||
}
|
||||
}
|
||||
return allowedIds;
|
||||
return Promise.all(
|
||||
chunks(albumIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.albumRepository
|
||||
.createQueryBuilder('album')
|
||||
.select('album.id')
|
||||
.leftJoin('album.sharedUsers', 'sharedUsers')
|
||||
.where('album.id IN (:...albumIds)', { albumIds: idChunk })
|
||||
.andWhere('album.isActivityEnabled = true')
|
||||
.andWhere(
|
||||
new Brackets((qb) => {
|
||||
qb.where('album.ownerId = :userId', { userId }).orWhere('sharedUsers.id = :userId', { userId });
|
||||
}),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
},
|
||||
|
||||
checkOwnerAccess: async (userId: string, assetIds: Set<string>): Promise<Set<string>> => {
|
||||
if (assetIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(assetIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.assetRepository
|
||||
.find({
|
||||
select: { id: true },
|
||||
where: {
|
||||
id: In(idChunk),
|
||||
ownerId: userId,
|
||||
},
|
||||
withDeleted: true,
|
||||
})
|
||||
.then((assets) => new Set(assets.map((asset) => asset.id))),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
},
|
||||
|
||||
checkPartnerAccess: async (userId: string, assetIds: Set<string>): Promise<Set<string>> => {
|
||||
if (assetIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(assetIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.partnerRepository
|
||||
.createQueryBuilder('partner')
|
||||
.innerJoin('partner.sharedBy', 'sharedBy')
|
||||
.innerJoin('sharedBy.assets', 'asset')
|
||||
.select('asset.id', 'assetId')
|
||||
.where('partner.sharedWithId = :userId', { userId })
|
||||
.andWhere('asset.id IN (:...assetIds)', { assetIds: idChunk })
|
||||
.getRawMany()
|
||||
.then((rows) => new Set(rows.map((row) => row.assetId))),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
},
|
||||
|
||||
checkSharedLinkAccess: async (sharedLinkId: string, assetIds: Set<string>): Promise<Set<string>> => {
|
||||
if (assetIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(assetIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.sharedLinkRepository
|
||||
.createQueryBuilder('sharedLink')
|
||||
.leftJoin('sharedLink.album', 'album')
|
||||
.leftJoin('sharedLink.assets', 'assets')
|
||||
.leftJoin('album.assets', 'albumAssets')
|
||||
.select('assets.id', 'assetId')
|
||||
.addSelect('albumAssets.id', 'albumAssetId')
|
||||
.addSelect('assets.livePhotoVideoId', 'assetLivePhotoVideoId')
|
||||
.addSelect('albumAssets.livePhotoVideoId', 'albumAssetLivePhotoVideoId')
|
||||
.where('sharedLink.id = :sharedLinkId', { sharedLinkId })
|
||||
.andWhere(
|
||||
'array["assets"."id", "assets"."livePhotoVideoId", "albumAssets"."id", "albumAssets"."livePhotoVideoId"] && array[:...assetIds]::uuid[]',
|
||||
{
|
||||
assetIds: idChunk,
|
||||
},
|
||||
)
|
||||
.getRawMany()
|
||||
.then((rows) => {
|
||||
const allowedIds = new Set<string>();
|
||||
for (const row of rows) {
|
||||
if (row.assetId && assetIds.has(row.assetId)) {
|
||||
allowedIds.add(row.assetId);
|
||||
}
|
||||
if (row.assetLivePhotoVideoId && assetIds.has(row.assetLivePhotoVideoId)) {
|
||||
allowedIds.add(row.assetLivePhotoVideoId);
|
||||
}
|
||||
if (row.albumAssetId && assetIds.has(row.albumAssetId)) {
|
||||
allowedIds.add(row.albumAssetId);
|
||||
}
|
||||
if (row.albumAssetLivePhotoVideoId && assetIds.has(row.albumAssetLivePhotoVideoId)) {
|
||||
allowedIds.add(row.albumAssetLivePhotoVideoId);
|
||||
}
|
||||
}
|
||||
return allowedIds;
|
||||
}),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
},
|
||||
};
|
||||
|
||||
authDevice = {
|
||||
checkOwnerAccess: async (userId: string, deviceIds: Set<string>): Promise<Set<string>> => {
|
||||
if (deviceIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(deviceIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.tokenRepository
|
||||
.find({
|
||||
select: { id: true },
|
||||
where: {
|
||||
userId,
|
||||
id: In(idChunk),
|
||||
},
|
||||
})
|
||||
.then((tokens) => new Set(tokens.map((token) => token.id))),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
},
|
||||
};
|
||||
|
||||
album = {
|
||||
checkOwnerAccess: async (userId: string, albumIds: Set<string>): Promise<Set<string>> => {
|
||||
if (albumIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(albumIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.albumRepository
|
||||
.find({
|
||||
select: { id: true },
|
||||
where: {
|
||||
id: In(idChunk),
|
||||
ownerId: userId,
|
||||
},
|
||||
})
|
||||
.then((albums) => new Set(albums.map((album) => album.id))),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
},
|
||||
|
||||
checkSharedAlbumAccess: async (userId: string, albumIds: Set<string>): Promise<Set<string>> => {
|
||||
if (albumIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(albumIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.albumRepository
|
||||
.find({
|
||||
select: { id: true },
|
||||
where: {
|
||||
id: In(idChunk),
|
||||
sharedUsers: {
|
||||
id: userId,
|
||||
},
|
||||
},
|
||||
})
|
||||
.then((albums) => new Set(albums.map((album) => album.id))),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
},
|
||||
|
||||
checkSharedLinkAccess: async (sharedLinkId: string, albumIds: Set<string>): Promise<Set<string>> => {
|
||||
if (albumIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(albumIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.sharedLinkRepository
|
||||
.find({
|
||||
select: { albumId: true },
|
||||
where: {
|
||||
id: sharedLinkId,
|
||||
albumId: In(idChunk),
|
||||
},
|
||||
})
|
||||
.then(
|
||||
(sharedLinks) =>
|
||||
new Set(sharedLinks.flatMap((sharedLink) => (!!sharedLink.albumId ? [sharedLink.albumId] : []))),
|
||||
),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
},
|
||||
};
|
||||
|
||||
person = {
|
||||
checkOwnerAccess: async (userId: string, personIds: Set<string>): Promise<Set<string>> => {
|
||||
if (personIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(personIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.personRepository
|
||||
.find({
|
||||
select: { id: true },
|
||||
where: {
|
||||
id: In(idChunk),
|
||||
ownerId: userId,
|
||||
},
|
||||
})
|
||||
.then((persons) => new Set(persons.map((person) => person.id))),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
},
|
||||
|
||||
checkFaceOwnerAccess: async (userId: string, assetFaceIds: Set<string>): Promise<Set<string>> => {
|
||||
if (assetFaceIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(assetFaceIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.assetFaceRepository
|
||||
.find({
|
||||
select: { id: true },
|
||||
where: {
|
||||
id: In(idChunk),
|
||||
asset: {
|
||||
ownerId: userId,
|
||||
},
|
||||
},
|
||||
})
|
||||
.then((faces) => new Set(faces.map((face) => face.id))),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
},
|
||||
};
|
||||
|
||||
partner = {
|
||||
checkUpdateAccess: async (userId: string, partnerIds: Set<string>): Promise<Set<string>> => {
|
||||
if (partnerIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(partnerIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.partnerRepository
|
||||
.createQueryBuilder('partner')
|
||||
.select('partner.sharedById')
|
||||
.where('partner.sharedById IN (:...partnerIds)', { partnerIds: idChunk })
|
||||
.andWhere('partner.sharedWithId = :userId', { userId })
|
||||
.getMany()
|
||||
.then((partners) => new Set(partners.map((partner) => partner.sharedById))),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
},
|
||||
};
|
||||
)
|
||||
.getMany()
|
||||
.then((albums) => new Set(albums.map((album) => album.id))),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
}
|
||||
}
|
||||
|
||||
class AlbumAccess implements IAlbumAccess {
|
||||
constructor(
|
||||
private albumRepository: Repository<AlbumEntity>,
|
||||
private sharedLinkRepository: Repository<SharedLinkEntity>,
|
||||
) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
async checkOwnerAccess(userId: string, albumIds: Set<string>): Promise<Set<string>> {
|
||||
if (albumIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(albumIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.albumRepository
|
||||
.find({
|
||||
select: { id: true },
|
||||
where: {
|
||||
id: In(idChunk),
|
||||
ownerId: userId,
|
||||
},
|
||||
})
|
||||
.then((albums) => new Set(albums.map((album) => album.id))),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
async checkSharedAlbumAccess(userId: string, albumIds: Set<string>): Promise<Set<string>> {
|
||||
if (albumIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(albumIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.albumRepository
|
||||
.find({
|
||||
select: { id: true },
|
||||
where: {
|
||||
id: In(idChunk),
|
||||
sharedUsers: {
|
||||
id: userId,
|
||||
},
|
||||
},
|
||||
})
|
||||
.then((albums) => new Set(albums.map((album) => album.id))),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
async checkSharedLinkAccess(sharedLinkId: string, albumIds: Set<string>): Promise<Set<string>> {
|
||||
if (albumIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(albumIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.sharedLinkRepository
|
||||
.find({
|
||||
select: { albumId: true },
|
||||
where: {
|
||||
id: sharedLinkId,
|
||||
albumId: In(idChunk),
|
||||
},
|
||||
})
|
||||
.then(
|
||||
(sharedLinks) =>
|
||||
new Set(sharedLinks.flatMap((sharedLink) => (!!sharedLink.albumId ? [sharedLink.albumId] : []))),
|
||||
),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
}
|
||||
}
|
||||
|
||||
class AssetAccess implements IAssetAccess {
|
||||
constructor(
|
||||
private albumRepository: Repository<AlbumEntity>,
|
||||
private assetRepository: Repository<AssetEntity>,
|
||||
private partnerRepository: Repository<PartnerEntity>,
|
||||
private sharedLinkRepository: Repository<SharedLinkEntity>,
|
||||
) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
async checkAlbumAccess(userId: string, assetIds: Set<string>): Promise<Set<string>> {
|
||||
if (assetIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(assetIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.albumRepository
|
||||
.createQueryBuilder('album')
|
||||
.innerJoin('album.assets', 'asset')
|
||||
.leftJoin('album.sharedUsers', 'sharedUsers')
|
||||
.select('asset.id', 'assetId')
|
||||
.addSelect('asset.livePhotoVideoId', 'livePhotoVideoId')
|
||||
.where('array["asset"."id", "asset"."livePhotoVideoId"] && array[:...assetIds]::uuid[]', {
|
||||
assetIds: idChunk,
|
||||
})
|
||||
.andWhere(
|
||||
new Brackets((qb) => {
|
||||
qb.where('album.ownerId = :userId', { userId }).orWhere('sharedUsers.id = :userId', { userId });
|
||||
}),
|
||||
)
|
||||
.getRawMany()
|
||||
.then((rows) => {
|
||||
const allowedIds = new Set<string>();
|
||||
for (const row of rows) {
|
||||
if (row.assetId && assetIds.has(row.assetId)) {
|
||||
allowedIds.add(row.assetId);
|
||||
}
|
||||
if (row.livePhotoVideoId && assetIds.has(row.livePhotoVideoId)) {
|
||||
allowedIds.add(row.livePhotoVideoId);
|
||||
}
|
||||
}
|
||||
return allowedIds;
|
||||
}),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
async checkOwnerAccess(userId: string, assetIds: Set<string>): Promise<Set<string>> {
|
||||
if (assetIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(assetIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.assetRepository
|
||||
.find({
|
||||
select: { id: true },
|
||||
where: {
|
||||
id: In(idChunk),
|
||||
ownerId: userId,
|
||||
},
|
||||
withDeleted: true,
|
||||
})
|
||||
.then((assets) => new Set(assets.map((asset) => asset.id))),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
async checkPartnerAccess(userId: string, assetIds: Set<string>): Promise<Set<string>> {
|
||||
if (assetIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(assetIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.partnerRepository
|
||||
.createQueryBuilder('partner')
|
||||
.innerJoin('partner.sharedBy', 'sharedBy')
|
||||
.innerJoin('sharedBy.assets', 'asset')
|
||||
.select('asset.id', 'assetId')
|
||||
.where('partner.sharedWithId = :userId', { userId })
|
||||
.andWhere('asset.id IN (:...assetIds)', { assetIds: idChunk })
|
||||
.getRawMany()
|
||||
.then((rows) => new Set(rows.map((row) => row.assetId))),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
async checkSharedLinkAccess(sharedLinkId: string, assetIds: Set<string>): Promise<Set<string>> {
|
||||
if (assetIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(assetIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.sharedLinkRepository
|
||||
.createQueryBuilder('sharedLink')
|
||||
.leftJoin('sharedLink.album', 'album')
|
||||
.leftJoin('sharedLink.assets', 'assets')
|
||||
.leftJoin('album.assets', 'albumAssets')
|
||||
.select('assets.id', 'assetId')
|
||||
.addSelect('albumAssets.id', 'albumAssetId')
|
||||
.addSelect('assets.livePhotoVideoId', 'assetLivePhotoVideoId')
|
||||
.addSelect('albumAssets.livePhotoVideoId', 'albumAssetLivePhotoVideoId')
|
||||
.where('sharedLink.id = :sharedLinkId', { sharedLinkId })
|
||||
.andWhere(
|
||||
'array["assets"."id", "assets"."livePhotoVideoId", "albumAssets"."id", "albumAssets"."livePhotoVideoId"] && array[:...assetIds]::uuid[]',
|
||||
{
|
||||
assetIds: idChunk,
|
||||
},
|
||||
)
|
||||
.getRawMany()
|
||||
.then((rows) => {
|
||||
const allowedIds = new Set<string>();
|
||||
for (const row of rows) {
|
||||
if (row.assetId && assetIds.has(row.assetId)) {
|
||||
allowedIds.add(row.assetId);
|
||||
}
|
||||
if (row.assetLivePhotoVideoId && assetIds.has(row.assetLivePhotoVideoId)) {
|
||||
allowedIds.add(row.assetLivePhotoVideoId);
|
||||
}
|
||||
if (row.albumAssetId && assetIds.has(row.albumAssetId)) {
|
||||
allowedIds.add(row.albumAssetId);
|
||||
}
|
||||
if (row.albumAssetLivePhotoVideoId && assetIds.has(row.albumAssetLivePhotoVideoId)) {
|
||||
allowedIds.add(row.albumAssetLivePhotoVideoId);
|
||||
}
|
||||
}
|
||||
return allowedIds;
|
||||
}),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
}
|
||||
}
|
||||
|
||||
class AuthDeviceAccess implements IAuthDeviceAccess {
|
||||
constructor(private tokenRepository: Repository<UserTokenEntity>) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
async checkOwnerAccess(userId: string, deviceIds: Set<string>): Promise<Set<string>> {
|
||||
if (deviceIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(deviceIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.tokenRepository
|
||||
.find({
|
||||
select: { id: true },
|
||||
where: {
|
||||
userId,
|
||||
id: In(idChunk),
|
||||
},
|
||||
})
|
||||
.then((tokens) => new Set(tokens.map((token) => token.id))),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
}
|
||||
}
|
||||
|
||||
class LibraryAccess implements ILibraryAccess {
|
||||
constructor(
|
||||
private libraryRepository: Repository<LibraryEntity>,
|
||||
private partnerRepository: Repository<PartnerEntity>,
|
||||
) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
async checkOwnerAccess(userId: string, libraryIds: Set<string>): Promise<Set<string>> {
|
||||
if (libraryIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(libraryIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.libraryRepository
|
||||
.find({
|
||||
select: { id: true },
|
||||
where: {
|
||||
id: In(idChunk),
|
||||
ownerId: userId,
|
||||
},
|
||||
})
|
||||
.then((libraries) => new Set(libraries.map((library) => library.id))),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
async checkPartnerAccess(userId: string, partnerIds: Set<string>): Promise<Set<string>> {
|
||||
if (partnerIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(partnerIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.partnerRepository
|
||||
.createQueryBuilder('partner')
|
||||
.select('partner.sharedById')
|
||||
.where('partner.sharedById IN (:...partnerIds)', { partnerIds: idChunk })
|
||||
.andWhere('partner.sharedWithId = :userId', { userId })
|
||||
.getMany()
|
||||
.then((partners) => new Set(partners.map((partner) => partner.sharedById))),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
}
|
||||
}
|
||||
|
||||
class TimelineAccess implements ITimelineAccess {
|
||||
constructor(private partnerRepository: Repository<PartnerEntity>) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
async checkPartnerAccess(userId: string, partnerIds: Set<string>): Promise<Set<string>> {
|
||||
if (partnerIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(partnerIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.partnerRepository
|
||||
.createQueryBuilder('partner')
|
||||
.select('partner.sharedById')
|
||||
.where('partner.sharedById IN (:...partnerIds)', { partnerIds: idChunk })
|
||||
.andWhere('partner.sharedWithId = :userId', { userId })
|
||||
.getMany()
|
||||
.then((partners) => new Set(partners.map((partner) => partner.sharedById))),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
}
|
||||
}
|
||||
|
||||
class PersonAccess implements IPersonAccess {
|
||||
constructor(
|
||||
private assetFaceRepository: Repository<AssetFaceEntity>,
|
||||
private personRepository: Repository<PersonEntity>,
|
||||
) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
async checkOwnerAccess(userId: string, personIds: Set<string>): Promise<Set<string>> {
|
||||
if (personIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(personIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.personRepository
|
||||
.find({
|
||||
select: { id: true },
|
||||
where: {
|
||||
id: In(idChunk),
|
||||
ownerId: userId,
|
||||
},
|
||||
})
|
||||
.then((persons) => new Set(persons.map((person) => person.id))),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
async checkFaceOwnerAccess(userId: string, assetFaceIds: Set<string>): Promise<Set<string>> {
|
||||
if (assetFaceIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(assetFaceIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.assetFaceRepository
|
||||
.find({
|
||||
select: { id: true },
|
||||
where: {
|
||||
id: In(idChunk),
|
||||
asset: {
|
||||
ownerId: userId,
|
||||
},
|
||||
},
|
||||
})
|
||||
.then((faces) => new Set(faces.map((face) => face.id))),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
}
|
||||
}
|
||||
|
||||
class PartnerAccess implements IPartnerAccess {
|
||||
constructor(private partnerRepository: Repository<PartnerEntity>) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
async checkUpdateAccess(userId: string, partnerIds: Set<string>): Promise<Set<string>> {
|
||||
if (partnerIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
chunks(partnerIds, DATABASE_PARAMETER_CHUNK_SIZE).map((idChunk) =>
|
||||
this.partnerRepository
|
||||
.createQueryBuilder('partner')
|
||||
.select('partner.sharedById')
|
||||
.where('partner.sharedById IN (:...partnerIds)', { partnerIds: idChunk })
|
||||
.andWhere('partner.sharedWithId = :userId', { userId })
|
||||
.getMany()
|
||||
.then((partners) => new Set(partners.map((partner) => partner.sharedById))),
|
||||
),
|
||||
).then((results) => setUnion(...results));
|
||||
}
|
||||
}
|
||||
|
||||
export class AccessRepository implements IAccessRepository {
|
||||
activity: IActivityAccess;
|
||||
album: IAlbumAccess;
|
||||
asset: IAssetAccess;
|
||||
authDevice: IAuthDeviceAccess;
|
||||
library: ILibraryAccess;
|
||||
person: IPersonAccess;
|
||||
partner: IPartnerAccess;
|
||||
timeline: ITimelineAccess;
|
||||
|
||||
constructor(
|
||||
@InjectRepository(ActivityEntity) activityRepository: Repository<ActivityEntity>,
|
||||
@InjectRepository(AssetEntity) assetRepository: Repository<AssetEntity>,
|
||||
@InjectRepository(AlbumEntity) albumRepository: Repository<AlbumEntity>,
|
||||
@InjectRepository(LibraryEntity) libraryRepository: Repository<LibraryEntity>,
|
||||
@InjectRepository(PartnerEntity) partnerRepository: Repository<PartnerEntity>,
|
||||
@InjectRepository(PersonEntity) personRepository: Repository<PersonEntity>,
|
||||
@InjectRepository(AssetFaceEntity) assetFaceRepository: Repository<AssetFaceEntity>,
|
||||
@InjectRepository(SharedLinkEntity) sharedLinkRepository: Repository<SharedLinkEntity>,
|
||||
@InjectRepository(UserTokenEntity) tokenRepository: Repository<UserTokenEntity>,
|
||||
) {
|
||||
this.activity = new ActivityAccess(activityRepository, albumRepository);
|
||||
this.album = new AlbumAccess(albumRepository, sharedLinkRepository);
|
||||
this.asset = new AssetAccess(albumRepository, assetRepository, partnerRepository, sharedLinkRepository);
|
||||
this.authDevice = new AuthDeviceAccess(tokenRepository);
|
||||
this.library = new LibraryAccess(libraryRepository, partnerRepository);
|
||||
this.person = new PersonAccess(assetFaceRepository, personRepository);
|
||||
this.partner = new PartnerAccess(partnerRepository);
|
||||
this.timeline = new TimelineAccess(partnerRepository);
|
||||
}
|
||||
}
|
||||
|
@ -98,8 +98,25 @@ class SqlGenerator {
|
||||
|
||||
const data: string[] = [`-- NOTE: This file is auto generated by ./sql-generator`];
|
||||
const instance = this.app.get<Repository>(Repository);
|
||||
const properties = Object.getOwnPropertyNames(Repository.prototype) as Array<keyof typeof Repository>;
|
||||
for (const key of properties) {
|
||||
|
||||
// normal repositories
|
||||
data.push(...(await this.runTargets(instance, `${Repository.name}`)));
|
||||
|
||||
// nested repositories
|
||||
if (Repository.name === AccessRepository.name) {
|
||||
for (const key of Object.keys(instance)) {
|
||||
const subInstance = (instance as any)[key];
|
||||
data.push(...(await this.runTargets(subInstance, `${Repository.name}.${key}`)));
|
||||
}
|
||||
}
|
||||
|
||||
this.results[Repository.name] = data;
|
||||
}
|
||||
|
||||
private async runTargets(instance: any, label: string) {
|
||||
const data: string[] = [];
|
||||
|
||||
for (const key of this.getPropertyNames(instance)) {
|
||||
const target = instance[key];
|
||||
if (!(target instanceof Function)) {
|
||||
continue;
|
||||
@ -116,7 +133,7 @@ class SqlGenerator {
|
||||
}
|
||||
|
||||
for (const { name, params } of queries) {
|
||||
let queryLabel = `${Repository.name}.${key}`;
|
||||
let queryLabel = `${label}.${key}`;
|
||||
if (name) {
|
||||
queryLabel += ` (${name})`;
|
||||
}
|
||||
@ -135,7 +152,7 @@ class SqlGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
this.results[Repository.name] = data;
|
||||
return data;
|
||||
}
|
||||
|
||||
private async write() {
|
||||
@ -156,6 +173,10 @@ class SqlGenerator {
|
||||
await this.app.close();
|
||||
}
|
||||
}
|
||||
|
||||
private getPropertyNames(instance: any): string[] {
|
||||
return Object.getOwnPropertyNames(Object.getPrototypeOf(instance)) as any[];
|
||||
}
|
||||
}
|
||||
|
||||
new SqlGenerator({ targetDir: './src/infra/sql' })
|
||||
|
@ -1 +1,243 @@
|
||||
-- NOTE: This file is auto generated by ./sql-generator
|
||||
|
||||
-- AccessRepository.activity.checkOwnerAccess
|
||||
SELECT
|
||||
"ActivityEntity"."id" AS "ActivityEntity_id"
|
||||
FROM
|
||||
"activity" "ActivityEntity"
|
||||
WHERE
|
||||
(
|
||||
"ActivityEntity"."id" IN ($1)
|
||||
AND "ActivityEntity"."userId" = $2
|
||||
)
|
||||
|
||||
-- AccessRepository.activity.checkAlbumOwnerAccess
|
||||
SELECT
|
||||
"ActivityEntity"."id" AS "ActivityEntity_id"
|
||||
FROM
|
||||
"activity" "ActivityEntity"
|
||||
LEFT JOIN "albums" "ActivityEntity__ActivityEntity_album" ON "ActivityEntity__ActivityEntity_album"."id" = "ActivityEntity"."albumId"
|
||||
AND (
|
||||
"ActivityEntity__ActivityEntity_album"."deletedAt" IS NULL
|
||||
)
|
||||
WHERE
|
||||
(
|
||||
"ActivityEntity"."id" IN ($1)
|
||||
AND "ActivityEntity__ActivityEntity_album"."ownerId" = $2
|
||||
)
|
||||
|
||||
-- AccessRepository.activity.checkCreateAccess
|
||||
SELECT
|
||||
"album"."id" AS "album_id"
|
||||
FROM
|
||||
"albums" "album"
|
||||
LEFT JOIN "albums_shared_users_users" "album_sharedUsers" ON "album_sharedUsers"."albumsId" = "album"."id"
|
||||
LEFT JOIN "users" "sharedUsers" ON "sharedUsers"."id" = "album_sharedUsers"."usersId"
|
||||
AND ("sharedUsers"."deletedAt" IS NULL)
|
||||
WHERE
|
||||
(
|
||||
"album"."id" IN ($1)
|
||||
AND "album"."isActivityEnabled" = true
|
||||
AND (
|
||||
"album"."ownerId" = $2
|
||||
OR "sharedUsers"."id" = $2
|
||||
)
|
||||
)
|
||||
AND ("album"."deletedAt" IS NULL)
|
||||
|
||||
-- AccessRepository.album.checkOwnerAccess
|
||||
SELECT
|
||||
"AlbumEntity"."id" AS "AlbumEntity_id"
|
||||
FROM
|
||||
"albums" "AlbumEntity"
|
||||
WHERE
|
||||
(
|
||||
(
|
||||
"AlbumEntity"."id" IN ($1)
|
||||
AND "AlbumEntity"."ownerId" = $2
|
||||
)
|
||||
)
|
||||
AND ("AlbumEntity"."deletedAt" IS NULL)
|
||||
|
||||
-- AccessRepository.album.checkSharedAlbumAccess
|
||||
SELECT
|
||||
"AlbumEntity"."id" AS "AlbumEntity_id"
|
||||
FROM
|
||||
"albums" "AlbumEntity"
|
||||
LEFT JOIN "albums_shared_users_users" "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers" ON "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."albumsId" = "AlbumEntity"."id"
|
||||
LEFT JOIN "users" "AlbumEntity__AlbumEntity_sharedUsers" ON "AlbumEntity__AlbumEntity_sharedUsers"."id" = "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."usersId"
|
||||
AND (
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."deletedAt" IS NULL
|
||||
)
|
||||
WHERE
|
||||
(
|
||||
(
|
||||
"AlbumEntity"."id" IN ($1)
|
||||
AND "AlbumEntity__AlbumEntity_sharedUsers"."id" = $2
|
||||
)
|
||||
)
|
||||
AND ("AlbumEntity"."deletedAt" IS NULL)
|
||||
|
||||
-- AccessRepository.album.checkSharedLinkAccess
|
||||
SELECT
|
||||
"SharedLinkEntity"."albumId" AS "SharedLinkEntity_albumId",
|
||||
"SharedLinkEntity"."id" AS "SharedLinkEntity_id"
|
||||
FROM
|
||||
"shared_links" "SharedLinkEntity"
|
||||
WHERE
|
||||
(
|
||||
"SharedLinkEntity"."id" = $1
|
||||
AND "SharedLinkEntity"."albumId" IN ($2)
|
||||
)
|
||||
|
||||
-- AccessRepository.asset.checkAlbumAccess
|
||||
SELECT
|
||||
"asset"."id" AS "assetId",
|
||||
"asset"."livePhotoVideoId" AS "livePhotoVideoId"
|
||||
FROM
|
||||
"albums" "album"
|
||||
INNER JOIN "albums_assets_assets" "album_asset" ON "album_asset"."albumsId" = "album"."id"
|
||||
INNER JOIN "assets" "asset" ON "asset"."id" = "album_asset"."assetsId"
|
||||
AND ("asset"."deletedAt" IS NULL)
|
||||
LEFT JOIN "albums_shared_users_users" "album_sharedUsers" ON "album_sharedUsers"."albumsId" = "album"."id"
|
||||
LEFT JOIN "users" "sharedUsers" ON "sharedUsers"."id" = "album_sharedUsers"."usersId"
|
||||
AND ("sharedUsers"."deletedAt" IS NULL)
|
||||
WHERE
|
||||
(
|
||||
array["asset"."id", "asset"."livePhotoVideoId"] && array[$1]::uuid []
|
||||
AND (
|
||||
"album"."ownerId" = $2
|
||||
OR "sharedUsers"."id" = $2
|
||||
)
|
||||
)
|
||||
AND ("album"."deletedAt" IS NULL)
|
||||
|
||||
-- AccessRepository.asset.checkOwnerAccess
|
||||
SELECT
|
||||
"AssetEntity"."id" AS "AssetEntity_id"
|
||||
FROM
|
||||
"assets" "AssetEntity"
|
||||
WHERE
|
||||
(
|
||||
"AssetEntity"."id" IN ($1)
|
||||
AND "AssetEntity"."ownerId" = $2
|
||||
)
|
||||
|
||||
-- AccessRepository.asset.checkPartnerAccess
|
||||
SELECT
|
||||
"asset"."id" AS "assetId"
|
||||
FROM
|
||||
"partners" "partner"
|
||||
INNER JOIN "users" "sharedBy" ON "sharedBy"."id" = "partner"."sharedById"
|
||||
AND ("sharedBy"."deletedAt" IS NULL)
|
||||
INNER JOIN "assets" "asset" ON "asset"."ownerId" = "sharedBy"."id"
|
||||
AND ("asset"."deletedAt" IS NULL)
|
||||
WHERE
|
||||
"partner"."sharedWithId" = $1
|
||||
AND "asset"."id" IN ($2)
|
||||
|
||||
-- AccessRepository.asset.checkSharedLinkAccess
|
||||
SELECT
|
||||
"assets"."id" AS "assetId",
|
||||
"assets"."livePhotoVideoId" AS "assetLivePhotoVideoId",
|
||||
"albumAssets"."id" AS "albumAssetId",
|
||||
"albumAssets"."livePhotoVideoId" AS "albumAssetLivePhotoVideoId"
|
||||
FROM
|
||||
"shared_links" "sharedLink"
|
||||
LEFT JOIN "albums" "album" ON "album"."id" = "sharedLink"."albumId"
|
||||
AND ("album"."deletedAt" IS NULL)
|
||||
LEFT JOIN "shared_link__asset" "assets_sharedLink" ON "assets_sharedLink"."sharedLinksId" = "sharedLink"."id"
|
||||
LEFT JOIN "assets" "assets" ON "assets"."id" = "assets_sharedLink"."assetsId"
|
||||
AND ("assets"."deletedAt" IS NULL)
|
||||
LEFT JOIN "albums_assets_assets" "album_albumAssets" ON "album_albumAssets"."albumsId" = "album"."id"
|
||||
LEFT JOIN "assets" "albumAssets" ON "albumAssets"."id" = "album_albumAssets"."assetsId"
|
||||
AND ("albumAssets"."deletedAt" IS NULL)
|
||||
WHERE
|
||||
"sharedLink"."id" = $1
|
||||
AND array[
|
||||
"assets"."id",
|
||||
"assets"."livePhotoVideoId",
|
||||
"albumAssets"."id",
|
||||
"albumAssets"."livePhotoVideoId"
|
||||
] && array[$2]::uuid []
|
||||
|
||||
-- AccessRepository.authDevice.checkOwnerAccess
|
||||
SELECT
|
||||
"UserTokenEntity"."id" AS "UserTokenEntity_id"
|
||||
FROM
|
||||
"user_token" "UserTokenEntity"
|
||||
WHERE
|
||||
(
|
||||
"UserTokenEntity"."userId" = $1
|
||||
AND "UserTokenEntity"."id" IN ($2)
|
||||
)
|
||||
|
||||
-- AccessRepository.library.checkOwnerAccess
|
||||
SELECT
|
||||
"LibraryEntity"."id" AS "LibraryEntity_id"
|
||||
FROM
|
||||
"libraries" "LibraryEntity"
|
||||
WHERE
|
||||
(
|
||||
(
|
||||
"LibraryEntity"."id" IN ($1)
|
||||
AND "LibraryEntity"."ownerId" = $2
|
||||
)
|
||||
)
|
||||
AND ("LibraryEntity"."deletedAt" IS NULL)
|
||||
|
||||
-- AccessRepository.library.checkPartnerAccess
|
||||
SELECT
|
||||
"partner"."sharedById" AS "partner_sharedById",
|
||||
"partner"."sharedWithId" AS "partner_sharedWithId"
|
||||
FROM
|
||||
"partners" "partner"
|
||||
WHERE
|
||||
"partner"."sharedById" IN ($1)
|
||||
AND "partner"."sharedWithId" = $2
|
||||
|
||||
-- AccessRepository.person.checkOwnerAccess
|
||||
SELECT
|
||||
"PersonEntity"."id" AS "PersonEntity_id"
|
||||
FROM
|
||||
"person" "PersonEntity"
|
||||
WHERE
|
||||
(
|
||||
"PersonEntity"."id" IN ($1)
|
||||
AND "PersonEntity"."ownerId" = $2
|
||||
)
|
||||
|
||||
-- AccessRepository.person.checkFaceOwnerAccess
|
||||
SELECT
|
||||
"AssetFaceEntity"."id" AS "AssetFaceEntity_id"
|
||||
FROM
|
||||
"asset_faces" "AssetFaceEntity"
|
||||
LEFT JOIN "assets" "AssetFaceEntity__AssetFaceEntity_asset" ON "AssetFaceEntity__AssetFaceEntity_asset"."id" = "AssetFaceEntity"."assetId"
|
||||
AND (
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."deletedAt" IS NULL
|
||||
)
|
||||
WHERE
|
||||
(
|
||||
"AssetFaceEntity"."id" IN ($1)
|
||||
AND "AssetFaceEntity__AssetFaceEntity_asset"."ownerId" = $2
|
||||
)
|
||||
|
||||
-- AccessRepository.partner.checkUpdateAccess
|
||||
SELECT
|
||||
"partner"."sharedById" AS "partner_sharedById",
|
||||
"partner"."sharedWithId" AS "partner_sharedWithId"
|
||||
FROM
|
||||
"partners" "partner"
|
||||
WHERE
|
||||
"partner"."sharedById" IN ($1)
|
||||
AND "partner"."sharedWithId" = $2
|
||||
|
||||
-- AccessRepository.timeline.checkPartnerAccess
|
||||
SELECT
|
||||
"partner"."sharedById" AS "partner_sharedById",
|
||||
"partner"."sharedWithId" AS "partner_sharedWithId"
|
||||
FROM
|
||||
"partners" "partner"
|
||||
WHERE
|
||||
"partner"."sharedById" IN ($1)
|
||||
AND "partner"."sharedWithId" = $2
|
||||
|
Loading…
Reference in New Issue
Block a user