Skip to content

Commit

Permalink
feat: add authorization, expo-image, update pkgs (#94)
Browse files Browse the repository at this point in the history
* 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
enfipy committed Feb 16, 2024
1 parent 1fe53ba commit 7833c6f
Show file tree
Hide file tree
Showing 25 changed files with 989 additions and 722 deletions.
4 changes: 2 additions & 2 deletions apps/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { fetchRequestHandler } from "@trpc/server/adapters/fetch"

import { appRouter } from "./router"
import { createContext, type Env } from "./trpc"
import { initLocalR2Api } from "./utils/localR2"
import { devApi } from "./utils/localR2"

const app = new Hono<{ Bindings: Env }>()

app.use("*", cors())

initLocalR2Api(app)
app.route("/dev", devApi)

// Handle all requests to /v1/* and pass them to the tRPC router
app.use("/v1/*", async (c) => {
Expand Down
14 changes: 14 additions & 0 deletions apps/api/src/router/auth.ts
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
}),
})
158 changes: 3 additions & 155 deletions apps/api/src/router/index.ts
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
158 changes: 158 additions & 0 deletions apps/api/src/router/video.ts
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
}),
})
Loading

0 comments on commit 7833c6f

Please sign in to comment.