Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 39 additions & 5 deletions src/controllers/dream.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@
transformDreamsWithSignedUrls,
} from "utils/transform.util";
import { detectMediaTypeFromExtension } from "utils/media.util";
import {
clearFilmstripVersion,
delThumbVersion,
getFilmstripVersion,
getThumbVersion,
setFilmstripVersion,
setThumbVersion,
} from "utils/uploadVersion.util";

/**
* Handles get dreams
Expand Down Expand Up @@ -372,17 +380,21 @@
* filePath r2 generation
*/
if (type === DreamFileType.THUMBNAIL) {
const renderVersion = await setThumbVersion(dreamUUID);
filePath = generateThumbnailPath({
userIdentifier,
dreamUUID,
extension: fileExtension,
renderVersion,
});
} else if (type === DreamFileType.FILMSTRIP) {
const renderVersion = await setFilmstripVersion(dreamUUID);
filePath = generateFilmstripPath({
userIdentifier,
dreamUUID,
extension: fileExtension,
frameNumber: frameNumber!,
renderVersion,
});
} else if (type === DreamFileType.DREAM) {
filePath = generateDreamPath({
Expand Down Expand Up @@ -478,13 +490,15 @@
userIdentifier,
dreamUUID,
extension: fileExtension,
renderVersion: await getThumbVersion(dreamUUID),
});
} else if (type === DreamFileType.FILMSTRIP) {
filePath = generateFilmstripPath({
userIdentifier,
dreamUUID,
extension: fileExtension,
frameNumber: frameNumber!,
renderVersion: await getFilmstripVersion(dreamUUID),
});
} else if (type === DreamFileType.DREAM) {
filePath = generateDreamPath({
Expand Down Expand Up @@ -579,6 +593,7 @@
userIdentifier,
dreamUUID,
extension: fileExtension,
renderVersion: await getThumbVersion(dreamUUID),
});

/**
Expand All @@ -587,12 +602,14 @@
await dreamRepository.update(dream.id, {
thumbnail: filePath,
});
await delThumbVersion(dreamUUID);
} else if (type === DreamFileType.FILMSTRIP) {
filePath = generateFilmstripPath({
userIdentifier,
dreamUUID,
extension: fileExtension,
frameNumber: frameNumber!,
renderVersion: await getFilmstripVersion(dreamUUID),
});
} else if (type === DreamFileType.DREAM && !processed) {
filePath = generateDreamPath({
Expand Down Expand Up @@ -1030,6 +1047,8 @@
return handleNotFound(req as RequestType, res);
}

await clearFilmstripVersion(dreamUUID);

const updatedDream = await dreamRepository.save({
...dream,
status: DreamStatusType.PROCESSING,
Expand Down Expand Up @@ -1091,15 +1110,25 @@
*/

const user = dream.user;
const filmstripVersion = filmstrip
? await getFilmstripVersion(dreamUUID)
: undefined;
const formatedFilmstrip: Frame[] | undefined = filmstrip?.map(
(frame) =>
({
frameNumber: Number(frame),
url: `${getUserIdentifier(
user,
)}/${dreamUUID}/filmstrip/frame-${frame}.${FILE_EXTENSIONS.JPG}`,
url: filmstripVersion
? `${getUserIdentifier(
user,

Check failure on line 1122 in src/controllers/dream.controller.ts

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

Expected indentation of 14 spaces but found 16
)}/${dreamUUID}/filmstrip/${filmstripVersion}/frame-${frame}.${

Check failure on line 1123 in src/controllers/dream.controller.ts

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

Expected indentation of 12 spaces but found 14
FILE_EXTENSIONS.JPG

Check failure on line 1124 in src/controllers/dream.controller.ts

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

Expected indentation of 14 spaces but found 16
}`

Check failure on line 1125 in src/controllers/dream.controller.ts

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

Expected indentation of 12 spaces but found 14
: `${getUserIdentifier(
user,

Check failure on line 1127 in src/controllers/dream.controller.ts

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

Expected indentation of 14 spaces but found 16
)}/${dreamUUID}/filmstrip/frame-${frame}.${FILE_EXTENSIONS.JPG}`,

Check failure on line 1128 in src/controllers/dream.controller.ts

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

Expected indentation of 12 spaces but found 14
}) as Frame,
);
if (filmstripVersion) await clearFilmstripVersion(dreamUUID);

const updateData: Partial<Dream> = {
status: DreamStatusType.PROCESSED,
Expand Down Expand Up @@ -1486,8 +1515,13 @@
const bucketName = env.R2_BUCKET_NAME;
const fileMymeType = req.file?.mimetype;
const fileExtension = MYME_TYPES_EXTENSIONS[fileMymeType ?? MYME_TYPES.MP4];
const fileName = `${dreamUUID}.${fileExtension}`;
const filePath = `${getUserIdentifier(user)}/${dreamUUID}/${fileName}`;
const userIdentifier = getUserIdentifier(dream.user);
const filePath = generateThumbnailPath({
userIdentifier,
dreamUUID,
extension: fileExtension,
renderVersion: Date.now(),
});

if (thumbnailBuffer) {
const command = new PutObjectCommand({
Expand Down
9 changes: 9 additions & 0 deletions src/controllers/keyframe.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ import {
generateKeyframePath,
getUploadPartSignedUrl,
} from "utils/r2.util";
import {
delKeyframeVersion,
getKeyframeVersion,
setKeyframeVersion,
} from "utils/uploadVersion.util";
import { CreateMultipartUploadFileRequest } from "types/keyframe.types";
import { keyframeRepository, userRepository } from "database/repositories";
import {
Expand Down Expand Up @@ -219,10 +224,12 @@ export const handleInitKeyframeImageUpload = async (
/**
* filePath r2 generation
*/
const renderVersion = await setKeyframeVersion(keyframeUUID);
const filePath = generateKeyframePath({
userIdentifier,
keyframeUUID,
extension: fileExtension,
renderVersion,
});

const uploadId = await createMultipartUpload(filePath!);
Expand Down Expand Up @@ -304,11 +311,13 @@ export const handleCompleteKeyframeImageUpload = async (
userIdentifier,
keyframeUUID,
extension: fileExtension,
renderVersion: await getKeyframeVersion(keyframeUUID),
});

await keyframeRepository.update(keyframe.id, {
image: filePath!,
});
await delKeyframeVersion(keyframeUUID);

/**
* completes multipart upload with path, upload id and parts
Expand Down
24 changes: 12 additions & 12 deletions src/controllers/playlist.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -453,15 +453,15 @@
const unfilteredReferences = await playlistItemRepository.find({
where: Array.isArray(where)
? where.map((playlistWhere) => ({
playlistItem: { uuid },
type: PlaylistItemType.PLAYLIST,
playlist: playlistWhere,
}))
playlistItem: { uuid },

Check failure on line 456 in src/controllers/playlist.controller.ts

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

Expected indentation of 10 spaces but found 12
type: PlaylistItemType.PLAYLIST,

Check failure on line 457 in src/controllers/playlist.controller.ts

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

Expected indentation of 10 spaces but found 12
playlist: playlistWhere,

Check failure on line 458 in src/controllers/playlist.controller.ts

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

Expected indentation of 10 spaces but found 12
}))

Check failure on line 459 in src/controllers/playlist.controller.ts

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

Expected indentation of 8 spaces but found 10
: {
playlistItem: { uuid },
type: PlaylistItemType.PLAYLIST,
playlist: where,
},
playlistItem: { uuid },
type: PlaylistItemType.PLAYLIST,
playlist: where,
},
select: {
id: true,
type: true,
Expand Down Expand Up @@ -557,9 +557,9 @@
const userCondition = { uuid: userUUID };
const where = searchILike
? [
{ user: userCondition, name: searchILike },
{ user: userCondition, displayedOwner: { name: searchILike } },
]
{ user: userCondition, name: searchILike },
{ user: userCondition, displayedOwner: { name: searchILike } },
]
: { user: userCondition };

try {
Expand Down Expand Up @@ -847,7 +847,7 @@
const fileMymeType = req.file?.mimetype;
const fileExtension =
MYME_TYPES_EXTENSIONS[fileMymeType ?? MYME_TYPES.JPEG];
const fileName = `${THUMBNAIL}.${fileExtension}`;
const fileName = `${THUMBNAIL}-${Date.now()}.${fileExtension}`;
const filePath = `${getUserIdentifier(user)}/${PLAYLIST_PREFIX}-${
playlist.id
}/${fileName}`;
Expand Down
20 changes: 10 additions & 10 deletions src/controllers/user.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,15 +142,15 @@ export const handleGetUsers = async (
};
const whereSentence = search
? [
{
...baseConditions,
name: ILike(`%${search}%`),
},
{
...baseConditions,
email: ILike(`%${search}%`),
},
]
{
...baseConditions,
name: ILike(`%${search}%`),
},
{
...baseConditions,
email: ILike(`%${search}%`),
},
]
: (baseConditions as FindOptionsWhere<User>);
const [users, count] = await userRepository.findAndCount({
where: whereSentence,
Expand Down Expand Up @@ -543,7 +543,7 @@ export const handleUpdateUserAvatar = async (
const fileMymeType = req.file?.mimetype;
const fileExtension =
MYME_TYPES_EXTENSIONS[fileMymeType ?? MYME_TYPES.JPEG];
const fileName = `${AVATAR}.${fileExtension}`;
const fileName = `${AVATAR}-${Date.now()}.${fileExtension}`;
const filePath = `${getUserIdentifier(user)}/${fileName}`;

if (avatarBuffer) {
Expand Down
19 changes: 16 additions & 3 deletions src/utils/r2.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,24 +140,33 @@ export const generateThumbnailPath = ({
userIdentifier,
dreamUUID,
extension,
renderVersion,
}: {
userIdentifier: string;
dreamUUID: string;
extension: string;
}) => `${userIdentifier}/${dreamUUID}/thumbnails/${dreamUUID}.${extension}`;
renderVersion?: number;
}) =>
renderVersion
? `${userIdentifier}/${dreamUUID}/thumbnails/${dreamUUID}-${renderVersion}.${extension}`
: `${userIdentifier}/${dreamUUID}/thumbnails/${dreamUUID}.${extension}`;

export const generateFilmstripPath = ({
userIdentifier,
dreamUUID,
extension,
frameNumber,
renderVersion,
}: {
userIdentifier: string;
dreamUUID: string;
extension: string;
frameNumber: number;
renderVersion?: number;
}) =>
`${userIdentifier}/${dreamUUID}/filmstrip/frame-${frameNumber}.${extension}`;
renderVersion
? `${userIdentifier}/${dreamUUID}/filmstrip/${renderVersion}/frame-${frameNumber}.${extension}`
: `${userIdentifier}/${dreamUUID}/filmstrip/frame-${frameNumber}.${extension}`;

export const generateDreamPath = ({
userIdentifier,
Expand All @@ -178,12 +187,16 @@ export const generateKeyframePath = ({
userIdentifier,
keyframeUUID,
extension,
renderVersion,
}: {
userIdentifier: string;
keyframeUUID: string;
extension: string;
renderVersion?: number;
}) =>
`${userIdentifier}/keyframes/${keyframeUUID}/${keyframeUUID}.${extension}`;
renderVersion
? `${userIdentifier}/keyframes/${keyframeUUID}/${keyframeUUID}-${renderVersion}.${extension}`
: `${userIdentifier}/keyframes/${keyframeUUID}/${keyframeUUID}.${extension}`;

/**
* Helper function to extract file extension from object key or file path
Expand Down
38 changes: 38 additions & 0 deletions src/utils/uploadVersion.util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { redisClient } from "clients/redis.client";

const TTL = 7200; // 2 hours — enough for any upload to complete

const toNumber = (val: string | null): number | undefined =>
val ? Number(val) : undefined;

const makeVersionStore = (prefix: string, nx = false) => ({
set: async (uuid: string): Promise<number> => {
const key = `upload:version:${prefix}:${uuid}`;
const version = Date.now();
if (nx) {
await redisClient.set(key, version, "EX", TTL, "NX");
return toNumber(await redisClient.get(key)) ?? version;
}
await redisClient.set(key, version, "EX", TTL);
return version;
},
get: (uuid: string) =>
redisClient.get(`upload:version:${prefix}:${uuid}`).then(toNumber),
del: (uuid: string) => redisClient.del(`upload:version:${prefix}:${uuid}`),
});

const thumbStore = makeVersionStore("thumb");
const filmstripStore = makeVersionStore("filmstrip", true);
const keyframeStore = makeVersionStore("keyframe");

export const setThumbVersion = thumbStore.set;
export const getThumbVersion = thumbStore.get;
export const delThumbVersion = thumbStore.del;

export const setFilmstripVersion = filmstripStore.set;
export const getFilmstripVersion = filmstripStore.get;
export const clearFilmstripVersion = filmstripStore.del;

export const setKeyframeVersion = keyframeStore.set;
export const getKeyframeVersion = keyframeStore.get;
export const delKeyframeVersion = keyframeStore.del;
16 changes: 15 additions & 1 deletion src/utils/workos.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ export const authenticateAndGetWorkOSSession = async (authToken: string) => {
});
};

// Deduplicates concurrent refresh attempts for the same expired session token.
// WorkOS refresh tokens are single-use, so only one refresh per token should be made.
const pendingRefreshes = new Map<
string,
Promise<{ authenticated: boolean; sealedSession?: string }>
>();

// Common authentication logic
export const authenticateWorkOS = async (
authToken: string | undefined,
Expand All @@ -64,7 +71,14 @@ export const authenticateWorkOS = async (
let session = await authenticateAndGetWorkOSSession(authToken);

if (!session) {
const refreshResult = await refreshWorkOSSession(authToken);
let refreshPromise = pendingRefreshes.get(authToken);
if (!refreshPromise) {
refreshPromise = refreshWorkOSSession(authToken).finally(() => {
pendingRefreshes.delete(authToken);
});
pendingRefreshes.set(authToken, refreshPromise);
}
const refreshResult = await refreshPromise;
if (!refreshResult.authenticated || !refreshResult.sealedSession) {
return null;
}
Expand Down
Loading