Skip to content

Commit

Permalink
Bug fixes (#18)
Browse files Browse the repository at this point in the history
* updated docker files

* change environment variables

* change entry scripts and dockerfiles

* added tunnel options
  • Loading branch information
orthdron authored Aug 13, 2024
1 parent f62274b commit 0f784a8
Show file tree
Hide file tree
Showing 20 changed files with 296 additions and 147 deletions.
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ npm-debug.log
Dockerfile
.dockerignore
.git
.env*
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ RAWFILES_S3_ENDPOINT=<custom_endpoint>
# In Megabytes
MAX_FILE_SIZE=<max_file_size_in_mb>
# Final upload location url
NEXT_PUBLIC_FILE_URL=<final_upload_location_url>
PROCESSED_VIDEO_URL=<final_upload_location_url>
WEBHOOK_TOKEN=<random_token>
MARK_FAILED_AFTER=600

Expand Down
17 changes: 13 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,19 @@ COPY . .

RUN npm run build

# Create a smaller production image
FROM node:22-slim AS runner
RUN npm install -g next

RUN apt-get update && apt-get install -y nginx && rm -rf /var/lib/apt/lists/*

WORKDIR /app

COPY --from=builder /app .
EXPOSE 3000
CMD ["node", "start.js"]

COPY docker/nginx.template /etc/nginx/conf.d/default.conf.template
COPY docker/entrypoint.sh /entrypoint.sh

RUN chmod +x /entrypoint.sh

EXPOSE 3000 4000

CMD ["/entrypoint.sh"]
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ RAWFILES_S3_ENDPOINT=<custom_endpoint>
# In Megabytes
MAX_FILE_SIZE=<max_file_size_in_mb>
# Final upload location url
NEXT_PUBLIC_FILE_URL= # public S3 domain where final transcoded videos are stored
PROCESSED_VIDEO_URL= # public S3 domain where final transcoded videos are stored
WEBHOOK_TOKEN= # Random Token for webhook notifications. Shared between this and transcoder.
# Optional variables
Expand Down
4 changes: 2 additions & 2 deletions app/(default)/(authorized)/videos/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ export default async function MyVideos() {
.where('userId', '=', user.id)
.execute();

const publicUrl = process.env.NEXT_PUBLIC_FILE_URL;
const publicUrl = process.env.PROCESSED_VIDEO_URL;

if (!publicUrl) {
throw new Error("NEXT_PUBLIC_FILE_URL is not defined");
throw new Error("PROCESSED_VIDEO_URL is not defined");
}

return (
Expand Down
18 changes: 9 additions & 9 deletions app/(default)/(public)/video/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,26 @@ export default async function Page({ params }: { params: { id: string } }) {
const { id } = params;
const video = await fetchVideoData(id);

if (!video || video.status != 'DONE') {
if (!video || video.status !== 'DONE') {
redirect('/');
}

const publicUrl = process.env.NEXT_PUBLIC_FILE_URL;
const publicUrl = process.env.PROCESSED_VIDEO_URL;
if (!publicUrl) {
throw new Error("NEXT_PUBLIC_FILE_URL is not defined");
throw new Error("PROCESSED_VIDEO_URL is not defined");
}

const videoProps = {
url: `${publicUrl}${id}/master.m3u8`,
vtt: `${publicUrl}${id}/sprite.vtt`,
poster: `${publicUrl}${id}/poster.jpeg`,
url: `${publicUrl}/${id}/master.m3u8`,
vtt: `${publicUrl}/${id}/sprite.vtt`,
poster: `${publicUrl}/${id}/poster.jpeg`,
...video
};

// Get the current domain
// Get the current domain and protocol
const headersList = headers();
const domain = headersList.get('host') || '';
const protocol = process.env.NODE_ENV === 'development' ? 'http' : 'https';
const protocol = headersList.get('X-Forwarded-Proto') || 'http';

const schema: VideoObject = {
'@type': 'VideoObject',
Expand Down Expand Up @@ -90,4 +90,4 @@ export default async function Page({ params }: { params: { id: string } }) {
</div>
</>
);
}
}
2 changes: 1 addition & 1 deletion app/api/auth/signup/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export async function POST(req: Request) {
return NextResponse.json({ error: "User already exists" }, { status: 400 });
}

db.insertInto("user").values({
await db.insertInto("user").values({
id: userId,
email: email.toLowerCase(),
userName: username.toLowerCase(),
Expand Down
26 changes: 21 additions & 5 deletions app/api/protected/upload/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ export async function POST(req: NextRequest) {
.returning(['id'])
.executeTakeFirstOrThrow();

// Update the Key to include the /uploads directory
const key = `uploads/${video.id}`;

const createMultipartUploadCommand = new CreateMultipartUploadCommand({
Expand All @@ -56,20 +55,37 @@ export async function POST(req: NextRequest) {
const multipartUpload = await s3client.send(createMultipartUploadCommand);
const uploadId = multipartUpload.UploadId;

// Generate presigned URLs for each part
const partCount = Math.ceil(size / PART_SIZE);
const presignedUrls = [];

for (let partNumber = 1; partNumber <= partCount; partNumber++) {
const command = new UploadPartCommand({
Bucket: process.env.RAWFILES_S3_BUCKET!,
Bucket: process.env.RAWFILES_S3_BUCKET,
Key: key,
UploadId: uploadId,
PartNumber: partNumber,
ContentLength: Math.min(PART_SIZE, size - (partNumber - 1) * PART_SIZE),
});
const signedUrl = await getSignedUrl(s3client, command, { expiresIn: 3600 });
presignedUrls.push({ partNumber, signedUrl });


const url = new URL(signedUrl);

if (process.env.ENABLE_TUNNEL) {
if (!process.env.TUNNEL_HOSTNAME) {
throw new Error("ENABLE_TUNNEL but missing TUNNEL_HOSTNAME");
}

if (!process.env.TUNNEL_PORT) {
throw new Error("ENABLE_TUNNEL but missing TUNNEL_PORT");
}

url.hostname = new URL(process.env.TUNNEL_HOSTNAME).hostname;
url.port = process.env.TUNNEL_PORT;
}


presignedUrls.push({ partNumber, signedUrl: url.toString() });
}

return NextResponse.json({
Expand All @@ -82,4 +98,4 @@ export async function POST(req: NextRequest) {
console.error("Error initiating multipart upload:", error);
return NextResponse.json({ error: "Failed to initiate upload" }, { status: 500 });
}
}
}
45 changes: 45 additions & 0 deletions app/api/proxy/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { NextRequest, NextResponse } from "next/server";

export async function GET(req: NextRequest) {
const url = req.nextUrl.clone();
const s3Endpoint = process.env.RAWFILES_S3_ENDPOINT;

// Ensure the URL is valid
if (!s3Endpoint) {
return NextResponse.json({ error: "S3 endpoint not configured." }, { status: 500 });
}

// Change the path to point to the S3 endpoint
url.hostname = new URL(s3Endpoint).hostname;
url.protocol = new URL(s3Endpoint).protocol;

const response = await fetch(url, {
method: req.method,
headers: req.headers,
body: req.body,
});

const responseBody = await response.text();
return NextResponse.json(responseBody, { status: response.status });
}

export async function POST(req: NextRequest) {
const url = req.nextUrl.clone();
const s3Endpoint = process.env.RAWFILES_S3_ENDPOINT;

if (!s3Endpoint) {
return NextResponse.json({ error: "S3 endpoint not configured." }, { status: 500 });
}

url.hostname = new URL(s3Endpoint).hostname;
url.protocol = new URL(s3Endpoint).protocol;

const response = await fetch(url, {
method: req.method,
headers: req.headers,
body: req.body,
});

const responseBody = await response.text();
return NextResponse.json(responseBody, { status: response.status });
}
10 changes: 5 additions & 5 deletions app/embed/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ export default async function Page({ params }: { params: { id: string } }) {
redirect('/');
}

const publicUrl = process.env.NEXT_PUBLIC_FILE_URL;
const publicUrl = process.env.PROCESSED_VIDEO_URL;
if (!publicUrl) {
throw new Error("NEXT_PUBLIC_FILE_URL is not defined");
throw new Error("PROCESSED_VIDEO_URL is not defined");
}

const videoProps = {
url: `${publicUrl}${id}/master.m3u8`,
vtt: `${publicUrl}${id}/sprite.vtt`,
poster: `${publicUrl}${id}/poster.jpeg`,
url: `${publicUrl}/${id}/master.m3u8`,
vtt: `${publicUrl}/${id}/sprite.vtt`,
poster: `${publicUrl}/${id}/poster.jpeg`,
...video
};

Expand Down
31 changes: 19 additions & 12 deletions components/video/VideoCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ interface Video {
title: string;
description: string;
createdAt: Date;
status: string;
}

const VideoCard: React.FC<{ video: Video; publicUrl: string }> = ({ video, publicUrl }) => {
Expand All @@ -15,18 +16,24 @@ const VideoCard: React.FC<{ video: Video; publicUrl: string }> = ({ video, publi
<div className="p-4">
<h2 className="text-xl font-bold mb-2 truncate">{video.title}</h2>
<p className="text-gray-600 mb-2 line-clamp-2">{video.description}</p>
<div className="flex space-x-4 mb-4">
<img
alt="Short preview"
src={`${baseUrl}${video.id}/short.gif`}
className="w-24 h-24 object-cover rounded"
/>
<img
alt="Long preview"
src={`${baseUrl}${video.id}/long.gif`}
className="w-24 h-24 object-cover rounded"
/>
</div>
{video.status === "DONE" ? (
<div className="flex space-x-4 mb-4">
<img
alt="Short preview"
src={`${baseUrl}${video.id}/short.gif`}
className="w-24 h-24 object-cover rounded"
/>
<img
alt="Long preview"
src={`${baseUrl}${video.id}/long.gif`}
className="w-24 h-24 object-cover rounded"
/>
</div>
) : (
<div className="mb-4 p-2 bg-gray-700 rounded text-center text-white">
<span className="text-lg font-semibold">Status: {video.status}</span>
</div>
)}
<div className="flex items-center justify-between text-sm text-gray-500">
<div className="flex items-center">
<span className="mr-1">🕒</span>
Expand Down
43 changes: 7 additions & 36 deletions components/video/VideoThumbnail.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,12 @@
"use client";
import Link from "next/link";
import { useState } from "react";
import VideoThumbnailHover from "./videoThumbnailHover";

export default function VideoThumbnail({ video }: { video: { id: string, title: string } }) {
const [isHovered, setIsHovered] = useState(false);
export default function VideoThumbnail({ video }: { video: { id: string; title: string } }) {

const { id, title } = video;
const baseUrl = process.env.PROCESSED_VIDEO_URL!;

let imgSrc, id, title;

if (!video) {
imgSrc = isHovered
? "https://picsum.photos/384/216?random=" + Math.random()
: `https://videos.subatic.com/${id}/short.gif`;
id = "abc";
title = "sample";
} else {
id = video.id;
imgSrc = isHovered
? `https://videos.subatic.com/${id}/long.gif`
: `https://videos.subatic.com/${id}/short.gif`;
title = video.title;
}
return (
<div className="mb-5">
<Link href={`/video/${id}`}>
<figure>
<img
className="mx-auto rounded-lg"
src={imgSrc}
alt="video transcoding...."
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
/>
<figcaption className="mt-2 text-center text-gray-500 text-md dark:text-gray-400">
{title}
</figcaption>
</figure>
</Link>
</div>
<VideoThumbnailHover video={{ id, title, baseUrl }} />
);
}
}
33 changes: 33 additions & 0 deletions components/video/videoThumbnailHover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"use client";
import Link from "next/link";
import { useState } from "react";

export default function VideoThumbnailHover({ video }: { video: { id: string; title: string, baseUrl: string } }) {
const [isHovered, setIsHovered] = useState(false);

const { id, title, baseUrl } = video;

const imgSrc = `${baseUrl}/${id}/${isHovered ? 'long' : 'short'}.gif`;

return (
<div className="mb-5">
<Link href={`/video/${id}`}>
<figure
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<img
className="mx-auto rounded-lg"
src={imgSrc}
alt={`Thumbnail for ${title}`}
/>
<figcaption className="mt-2 text-center text-gray-500 text-md dark:text-gray-400">
{title}
</figcaption>
</figure>
</Link>
</div>
);


}
Loading

0 comments on commit 0f784a8

Please sign in to comment.