-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add authorization, expo-image, update pkgs (#94)
* chore: prepare for adding auth * feat: add pages and wip auth page * feat: add expo-image and apple-sign-in * feat: add facebook and apple login ui elements
- Loading branch information
Showing
25 changed files
with
989 additions
and
722 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { sql } from "drizzle-orm" | ||
import { procedure, router } from "../trpc" | ||
import { users } from "../db/schema" | ||
|
||
export const videoRouter = router({ | ||
getUsers: procedure.query(async ({ ctx }) => { | ||
const foundUsers = await ctx.db | ||
.select() | ||
.from(users) | ||
.orderBy(sql`RANDOM()`) | ||
.limit(10) | ||
return foundUsers | ||
}), | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,160 +1,8 @@ | ||
import { AwsClient } from "aws4fetch" | ||
import z from "zod" | ||
import { createId } from "@paralleldrive/cuid2" | ||
import { TRPCError } from "@trpc/server" | ||
import { eq, sql } from "drizzle-orm" | ||
import { Storage, VideoUploadStatus } from "../db/types" | ||
import { getVideoUrl } from "../utils/storage" | ||
import { type Env, procedure, router } from "../trpc" | ||
import { dateNow } from "../utils/date" | ||
import { users, videos } from "../db/schema" | ||
import { type VideoProps, Environment } from "../types" | ||
|
||
const getUploadUrls = async (env: Env, videoId: string, partNames: string[]) => { | ||
const m3u8File = partNames.find((v) => v.includes(".m3u8")) | ||
if (!m3u8File) { | ||
throw new TRPCError({ | ||
code: "BAD_REQUEST", | ||
message: "Missing m3u8 file", | ||
}) | ||
} | ||
|
||
const r2 = new AwsClient({ | ||
accessKeyId: env.CF_ACCESS_KEY_ID, | ||
secretAccessKey: env.CF_SECRET_ACCESS_KEY, | ||
}) | ||
const bucketName = env.CF_BUCKET_NAME | ||
const accountId = env.CF_ACCOUNT_ID | ||
const uploadUrl = `https://${bucketName}.${accountId}.r2.cloudflarestorage.com` | ||
|
||
const uploadUrls: { [partName: string]: string } = {} | ||
for (const partName of partNames) { | ||
// If local development - use a local endpoint to upload the file | ||
if (env.ENVIRONMENT === Environment.DEV) { | ||
uploadUrls[partName] = `/dev/uploadFile/${videoId}/${partName}` | ||
continue | ||
} | ||
const url = new URL(uploadUrl) | ||
// Preserve the original path | ||
url.pathname = `/${videoId}/${partName}` | ||
// Specify a custom expiry for the presigned URL, in seconds | ||
url.searchParams.set("X-Amz-Expires", "3600") | ||
const signed = await r2.sign( | ||
new Request(url, { | ||
method: "PUT", | ||
}), | ||
{ | ||
aws: { signQuery: true }, | ||
// TODO: Add time constraints to request | ||
// headers: { | ||
// "If-Unmodified-Since": "Tue, 28 Sep 2021 16:00:00 GMT", | ||
// }, | ||
}, | ||
) | ||
uploadUrls[partName] = signed.url | ||
} | ||
return uploadUrls | ||
} | ||
import { router } from "../trpc" | ||
import { videoRouter } from "./video" | ||
|
||
export const appRouter = router({ | ||
getUploadVideoURL: procedure | ||
.input( | ||
z.object({ | ||
partNames: z.array(z.string()), | ||
}), | ||
) | ||
.mutation(async ({ ctx, input }) => { | ||
const videoId = createId() | ||
const uploadUrls = await getUploadUrls(ctx.env, videoId, input.partNames) | ||
|
||
// TODO: Remove this | ||
await ctx.db | ||
.insert(users) | ||
.values({ | ||
id: "user12", | ||
email: "user12", | ||
username: "user12", | ||
}) | ||
.onConflictDoNothing() | ||
|
||
// TODO: Check if user already has an untitled and unuploaded video and update it | ||
await ctx.db.insert(videos).values({ | ||
id: videoId, | ||
title: "untitled video", | ||
thumbnailIndex: 0, | ||
storage: Storage.PRIME_R2_BUCKET, | ||
segmentsNumber: input.partNames.filter((v) => v.includes(".ts")).length, | ||
uploadStatus: VideoUploadStatus.Uploading, | ||
// References | ||
authorId: "user12", | ||
}) | ||
|
||
return { | ||
videoId, | ||
uploadUrls, | ||
} | ||
}), | ||
updateVideo: procedure | ||
.input( | ||
z.object({ | ||
videoId: z.string(), | ||
}), | ||
) | ||
.mutation(async ({ ctx, input }) => { | ||
const foundVideos = await ctx.db | ||
.select() | ||
.from(videos) | ||
.where(eq(videos.id, input.videoId)) | ||
const foundVideo = foundVideos[0] | ||
if (!foundVideo) { | ||
throw new TRPCError({ | ||
code: "NOT_FOUND", | ||
message: "Video not found", | ||
}) | ||
} | ||
|
||
await ctx.db | ||
.update(videos) | ||
.set({ | ||
uploadStatus: VideoUploadStatus.Ready, | ||
updatedAt: dateNow(), | ||
}) | ||
.where(eq(videos.id, foundVideo.id)) | ||
console.log(foundVideos) | ||
|
||
return getVideoUrl(foundVideo.id, foundVideo.storage, ctx.env, ctx.req.url) | ||
}), | ||
getVideos: procedure.query(async ({ ctx }) => { | ||
const foundVideos = await ctx.db | ||
.select() | ||
.from(videos) | ||
.orderBy(sql`RANDOM()`) | ||
.limit(10) | ||
|
||
const recommendedVideos: VideoProps[] = foundVideos.map((v) => { | ||
const urlVideo = getVideoUrl(v.id, v.storage, ctx.env, ctx.req.url) | ||
const urlPoster = urlVideo.replace("video.m3u8", `thumbnail${v.thumbnailIndex}.png`) | ||
return { | ||
id: v.id, | ||
createdAt: v.createdAt, | ||
urlVideo, | ||
urlPoster, | ||
description: "sample description", // v.description, | ||
category: { | ||
icon: "dribbble", | ||
name: "Technology", | ||
type: "Series", | ||
}, | ||
author: { | ||
id: "user12", | ||
username: "user12", | ||
displayName: "user12", | ||
uriAvatar: "https://i.imgur.com/ljZTgRN.jpeg", | ||
}, | ||
} as VideoProps | ||
}) | ||
return recommendedVideos | ||
}), | ||
video: videoRouter, | ||
}) | ||
|
||
export type AppRouter = typeof appRouter |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
import { AwsClient } from "aws4fetch" | ||
import z from "zod" | ||
import { createId } from "@paralleldrive/cuid2" | ||
import { TRPCError } from "@trpc/server" | ||
import { eq, sql } from "drizzle-orm" | ||
import { Storage, VideoUploadStatus } from "../db/types" | ||
import { getVideoUrl } from "../utils/storage" | ||
import { type Env, procedure, router } from "../trpc" | ||
import { dateNow } from "../utils/date" | ||
import { users, videos } from "../db/schema" | ||
import { type VideoProps, Environment } from "../types" | ||
|
||
const getUploadUrls = async (env: Env, videoId: string, partNames: string[]) => { | ||
const m3u8File = partNames.find((v) => v.includes(".m3u8")) | ||
if (!m3u8File) { | ||
throw new TRPCError({ | ||
code: "BAD_REQUEST", | ||
message: "Missing m3u8 file", | ||
}) | ||
} | ||
|
||
const r2 = new AwsClient({ | ||
accessKeyId: env.CF_ACCESS_KEY_ID, | ||
secretAccessKey: env.CF_SECRET_ACCESS_KEY, | ||
}) | ||
const bucketName = env.CF_BUCKET_NAME | ||
const accountId = env.CF_ACCOUNT_ID | ||
const uploadUrl = `https://${bucketName}.${accountId}.r2.cloudflarestorage.com` | ||
|
||
const uploadUrls: { [partName: string]: string } = {} | ||
for (const partName of partNames) { | ||
// If local development - use a local endpoint to upload the file | ||
if (env.ENVIRONMENT === Environment.DEV) { | ||
uploadUrls[partName] = `/dev/uploadFile/${videoId}/${partName}` | ||
continue | ||
} | ||
const url = new URL(uploadUrl) | ||
// Preserve the original path | ||
url.pathname = `/${videoId}/${partName}` | ||
// Specify a custom expiry for the presigned URL, in seconds | ||
url.searchParams.set("X-Amz-Expires", "3600") | ||
const signed = await r2.sign( | ||
new Request(url, { | ||
method: "PUT", | ||
}), | ||
{ | ||
aws: { signQuery: true }, | ||
// TODO: Add time constraints to request | ||
// headers: { | ||
// "If-Unmodified-Since": "Tue, 28 Sep 2021 16:00:00 GMT", | ||
// }, | ||
}, | ||
) | ||
uploadUrls[partName] = signed.url | ||
} | ||
return uploadUrls | ||
} | ||
|
||
export const videoRouter = router({ | ||
getUploadVideoURL: procedure | ||
.input( | ||
z.object({ | ||
partNames: z.array(z.string()), | ||
}), | ||
) | ||
.mutation(async ({ ctx, input }) => { | ||
const videoId = createId() | ||
const uploadUrls = await getUploadUrls(ctx.env, videoId, input.partNames) | ||
|
||
// TODO: Remove this | ||
await ctx.db | ||
.insert(users) | ||
.values({ | ||
id: "user12", | ||
email: "user12", | ||
username: "user12", | ||
}) | ||
.onConflictDoNothing() | ||
|
||
// TODO: Check if user already has an untitled and unuploaded video and update it | ||
await ctx.db.insert(videos).values({ | ||
id: videoId, | ||
title: "untitled video", | ||
thumbnailIndex: 0, | ||
storage: Storage.PRIME_R2_BUCKET, | ||
segmentsNumber: input.partNames.filter((v) => v.includes(".ts")).length, | ||
uploadStatus: VideoUploadStatus.Uploading, | ||
// References | ||
authorId: "user12", | ||
}) | ||
|
||
return { | ||
videoId, | ||
uploadUrls, | ||
} | ||
}), | ||
updateVideo: procedure | ||
.input( | ||
z.object({ | ||
videoId: z.string(), | ||
}), | ||
) | ||
.mutation(async ({ ctx, input }) => { | ||
const foundVideos = await ctx.db | ||
.select() | ||
.from(videos) | ||
.where(eq(videos.id, input.videoId)) | ||
const foundVideo = foundVideos[0] | ||
if (!foundVideo) { | ||
throw new TRPCError({ | ||
code: "NOT_FOUND", | ||
message: "Video not found", | ||
}) | ||
} | ||
|
||
await ctx.db | ||
.update(videos) | ||
.set({ | ||
uploadStatus: VideoUploadStatus.Ready, | ||
updatedAt: dateNow(), | ||
}) | ||
.where(eq(videos.id, foundVideo.id)) | ||
console.log(foundVideos) | ||
|
||
return getVideoUrl(foundVideo.id, foundVideo.storage, ctx.env, ctx.req.url) | ||
}), | ||
getVideos: procedure.query(async ({ ctx }) => { | ||
const foundVideos = await ctx.db | ||
.select() | ||
.from(videos) | ||
.orderBy(sql`RANDOM()`) | ||
.limit(10) | ||
|
||
const recommendedVideos: VideoProps[] = foundVideos.map((v) => { | ||
const urlVideo = getVideoUrl(v.id, v.storage, ctx.env, ctx.req.url) | ||
const urlPoster = urlVideo.replace("video.m3u8", `thumbnail${v.thumbnailIndex}.png`) | ||
return { | ||
id: v.id, | ||
createdAt: v.createdAt, | ||
urlVideo, | ||
urlPoster, | ||
description: "sample description", // v.description, | ||
category: { | ||
icon: "dribbble", | ||
name: "Technology", | ||
type: "Series", | ||
}, | ||
author: { | ||
id: "user12", | ||
username: "user12", | ||
displayName: "user12", | ||
uriAvatar: "https://i.imgur.com/ljZTgRN.jpeg", | ||
}, | ||
} as VideoProps | ||
}) | ||
return recommendedVideos | ||
}), | ||
}) |
Oops, something went wrong.