mirror of
https://github.com/fergalmoran/mixyboos.git
synced 2025-12-22 09:41:39 +00:00
Gonna try something dangerous
This commit is contained in:
50
package.json
50
package.json
@@ -5,21 +5,18 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"devq": "concurrently 'next dev' 'quirrel'",
|
|
||||||
"ssl": "NODE_ENV=development node ./server.js",
|
"ssl": "NODE_ENV=development node ./server.js",
|
||||||
"sslq": "concurrently 'NODE_ENV=development node ./server.js' 'quirrel'",
|
|
||||||
"quirrel": "quirrel",
|
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"test": "export $(xargs < .env) && jest"
|
"test": "export $(xargs < .env) && jest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "^1.7.16",
|
"@headlessui/react": "^1.7.17",
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
"@radix-ui/react-progress": "^1.0.3",
|
"@radix-ui/react-progress": "^1.0.3",
|
||||||
"@radix-ui/react-toast": "^1.1.4",
|
"@radix-ui/react-toast": "^1.1.4",
|
||||||
"@radix-ui/react-tooltip": "^1.0.6",
|
"@radix-ui/react-tooltip": "^1.0.6",
|
||||||
"@sentry/nextjs": "^7.62.0",
|
"@sentry/nextjs": "^7.64.0",
|
||||||
"@sindresorhus/slugify": "^2.2.1",
|
"@sindresorhus/slugify": "^2.2.1",
|
||||||
"@t3-oss/env-nextjs": "^0.6.0",
|
"@t3-oss/env-nextjs": "^0.6.0",
|
||||||
"@tanstack/react-query": "^4.32.6",
|
"@tanstack/react-query": "^4.32.6",
|
||||||
@@ -33,29 +30,28 @@
|
|||||||
"import-local": "^3.1.0",
|
"import-local": "^3.1.0",
|
||||||
"install": "^0.13.0",
|
"install": "^0.13.0",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"next": "13.4.13",
|
"next": "13.4.17",
|
||||||
"next-auth": "^4.22.5",
|
"next-auth": "^4.23.1",
|
||||||
"pg": "^8.11.2",
|
"pg": "^8.11.3",
|
||||||
"pino": "^8.15.0",
|
"pino": "^8.15.0",
|
||||||
"pino-logflare": "^0.4.0",
|
"pino-logflare": "^0.4.0",
|
||||||
"postgres": "^3.3.5",
|
"postgres": "^3.3.5",
|
||||||
"quirrel": "^1.14.1",
|
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-hot-toast": "^2.4.1",
|
"react-hot-toast": "^2.4.1",
|
||||||
"react-icons": "^4.10.1",
|
"react-icons": "^4.10.1",
|
||||||
"superjson": "1.13.1",
|
"superjson": "1.13.1",
|
||||||
"yup": "^1.2.0",
|
"yup": "^1.2.0",
|
||||||
"zod": "^3.21.4",
|
"zod": "^3.22.1",
|
||||||
"zustand": "^4.4.1"
|
"zustand": "^4.4.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@azure/identity": "^3.2.4",
|
"@azure/identity": "^3.3.0",
|
||||||
"@azure/storage-blob": "^12.15.0",
|
"@azure/storage-blob": "^12.15.0",
|
||||||
"@faker-js/faker": "^8.0.2",
|
"@faker-js/faker": "^8.0.2",
|
||||||
"@hookform/resolvers": "^3.2.0",
|
"@hookform/resolvers": "^3.2.0",
|
||||||
"@ianvs/prettier-plugin-sort-imports": "^4.1.0",
|
"@ianvs/prettier-plugin-sort-imports": "^4.1.0",
|
||||||
"@next/env": "^13.4.13",
|
"@next/env": "^13.4.17",
|
||||||
"@radix-ui/react-avatar": "^1.0.3",
|
"@radix-ui/react-avatar": "^1.0.3",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
||||||
"@radix-ui/react-label": "^2.0.2",
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
@@ -65,37 +61,37 @@
|
|||||||
"@types/eslint": "^8.44.2",
|
"@types/eslint": "^8.44.2",
|
||||||
"@types/jest": "^29.5.3",
|
"@types/jest": "^29.5.3",
|
||||||
"@types/mime-types": "^2.1.1",
|
"@types/mime-types": "^2.1.1",
|
||||||
"@types/node": "^20.4.9",
|
"@types/node": "^20.5.0",
|
||||||
"@types/pg": "^8.10.2",
|
"@types/pg": "^8.10.2",
|
||||||
"@types/react": "^18.2.19",
|
"@types/react": "^18.2.20",
|
||||||
"@types/react-dom": "^18.2.7",
|
"@types/react-dom": "^18.2.7",
|
||||||
"@types/superagent": "^4.1.18",
|
"@types/superagent": "^4.1.18",
|
||||||
"@types/uuid": "^9.0.2",
|
"@types/uuid": "^9.0.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.3.0",
|
"@typescript-eslint/eslint-plugin": "^6.4.0",
|
||||||
"@typescript-eslint/parser": "^6.3.0",
|
"@typescript-eslint/parser": "^6.4.0",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.15",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"concurrently": "^8.2.0",
|
"concurrently": "^8.2.0",
|
||||||
"dayjs": "^1.11.9",
|
"dayjs": "^1.11.9",
|
||||||
"eslint": "^8.46.0",
|
"eslint": "^8.47.0",
|
||||||
"eslint-config-next": "^13.4.13",
|
"eslint-config-next": "^13.4.17",
|
||||||
"hls.js": "^1.4.10",
|
"hls.js": "^1.4.10",
|
||||||
"jest": "^29.6.2",
|
"jest": "^29.6.2",
|
||||||
"lucide-react": "^0.265.0",
|
"lucide-react": "^0.268.0",
|
||||||
"next-themes": "^0.2.1",
|
"next-themes": "^0.2.1",
|
||||||
"node-mocks-http": "^1.12.2",
|
"node-mocks-http": "^1.13.0",
|
||||||
"postcss": "^8.4.27",
|
"pino-pretty": "^10.2.0",
|
||||||
"prettier": "3.0.1",
|
"postcss": "^8.4.28",
|
||||||
"prettier-plugin-tailwindcss": "^0.4.1",
|
"prettier": "3.0.2",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.5.3",
|
||||||
"pusher": "^5.1.3",
|
"pusher": "^5.1.3",
|
||||||
"pusher-js": "^8.3.0",
|
"pusher-js": "^8.3.0",
|
||||||
"react-copy-to-clipboard": "^5.1.0",
|
"react-copy-to-clipboard": "^5.1.0",
|
||||||
"react-dropzone": "^14.2.3",
|
"react-dropzone": "^14.2.3",
|
||||||
"react-hook-form": "^7.45.4",
|
"react-hook-form": "^7.45.4",
|
||||||
"recharts": "^2.7.3",
|
|
||||||
"retry": "^0.13.1",
|
"retry": "^0.13.1",
|
||||||
"superagent": "^8.0.9",
|
"superagent": "^8.1.2",
|
||||||
"tailwind-merge": "^1.14.0",
|
"tailwind-merge": "^1.14.0",
|
||||||
"tailwindcss": "^3.3.3",
|
"tailwindcss": "^3.3.3",
|
||||||
"tailwindcss-animate": "^1.0.6",
|
"tailwindcss-animate": "^1.0.6",
|
||||||
@@ -103,7 +99,7 @@
|
|||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"typescript": "^5.1.6",
|
"typescript": "^5.1.6",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"vitest": "^0.34.1"
|
"vitest": "^0.34.2"
|
||||||
},
|
},
|
||||||
"ct3aMetadata": {
|
"ct3aMetadata": {
|
||||||
"initVersion": "7.12.2"
|
"initVersion": "7.12.2"
|
||||||
|
|||||||
2892
pnpm-lock.yaml
generated
2892
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,22 +1,20 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Loading from "@/lib/components/widgets/Loading";
|
import Loading from "@/lib/components/widgets/Loading";
|
||||||
import { api } from "@/lib/utils/api";
|
|
||||||
import { type NextPage } from "next";
|
import { type NextPage } from "next";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import ProfileEditForm from "../../components/profile-edit-form";
|
import ProfileEditForm from "../../components/profile-edit-form";
|
||||||
|
|
||||||
const ProfileSettingsPage: NextPage = () => {
|
const ProfileSettingsPage: NextPage = () => {
|
||||||
const { data: profile, status } = api.user.getProfileForSettings.useQuery();
|
return <Loading />;
|
||||||
|
// if (status === "loading") {
|
||||||
if (status === "loading") {
|
// return <Loading />;
|
||||||
return <Loading />;
|
// }
|
||||||
}
|
// return profile ? (
|
||||||
return profile ? (
|
// <ProfileEditForm profile={profile} />
|
||||||
<ProfileEditForm profile={profile} />
|
// ) : (
|
||||||
) : (
|
// <div>Nothing here bai!</div>
|
||||||
<div>Nothing here bai!</div>
|
// );
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ProfileSettingsPage;
|
export default ProfileSettingsPage;
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
"use client";
|
|
||||||
import LargeAudioPlayer from "@/components/widgets/audio/large-audio-player";
|
import LargeAudioPlayer from "@/components/widgets/audio/large-audio-player";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { api } from "@/lib/utils/api";
|
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
import MixService from "@/lib/services/api/mix-service";
|
||||||
|
|
||||||
export default function Page({
|
export default async function Page({
|
||||||
params,
|
params,
|
||||||
}: {
|
}: {
|
||||||
params: { username: string; mixSlug: string };
|
params: { username: string; mixSlug: string };
|
||||||
}) {
|
}) {
|
||||||
const mixQuery = api.mix.getByUserAndSlug.useQuery({
|
const mix = await new MixService().getByUserAndSlug(
|
||||||
username: params.username,
|
params.username,
|
||||||
mixSlug: params.mixSlug,
|
params.mixSlug,
|
||||||
});
|
);
|
||||||
const mix = mixQuery?.data;
|
|
||||||
return (
|
return (
|
||||||
<div className="container p-5">
|
<div className="container p-5">
|
||||||
{mix ? (
|
{mix ? (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { authOptions } from "@/lib/services/api/config";
|
import { authOptions } from "@/lib/services/auth/config";
|
||||||
import NextAuth from "next-auth";
|
import NextAuth from "next-auth";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
import { NextResponse } from "next/server";
|
|
||||||
|
|
||||||
export function GET() {
|
|
||||||
return NextResponse.json({ debug: "deez nuts" });
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import { processMixQueue } from "@/app/api/queues/upload/mix/route";
|
|
||||||
import { StatusCodes } from "http-status-codes";
|
|
||||||
import { type NextRequest } from "next/server";
|
|
||||||
import path from "path";
|
|
||||||
import os from "os";
|
|
||||||
import fs from "fs";
|
|
||||||
|
|
||||||
const extensions = ["mp3", "wav", "aiff", "mp4", "ogg"];
|
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
|
||||||
const { mixId } = (await req.json()) as { mixId: string };
|
|
||||||
|
|
||||||
let fileName = "";
|
|
||||||
let exists = false;
|
|
||||||
for (const extension of extensions) {
|
|
||||||
fileName = path.join(os.tmpdir(), `${mixId}.${extension}`);
|
|
||||||
if (fs.existsSync(fileName)) {
|
|
||||||
exists = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (exists) {
|
|
||||||
await processMixQueue.enqueue({ filePath: fileName, mixId }, { delay: 1 });
|
|
||||||
return StatusCodes.OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
return StatusCodes.NO_CONTENT;
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
import { waitForShowQueue } from "@/app/api/queues/shows/wait/route";
|
|
||||||
import { LiveShowStatus, liveShows, users } from "@/db/schema";
|
|
||||||
import { db } from "@/server/db";
|
|
||||||
import { and, eq, or } from "drizzle-orm";
|
|
||||||
import { StatusCodes } from "http-status-codes";
|
|
||||||
import { NextResponse, type NextRequest } from "next/server";
|
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
|
||||||
if (!req.body) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ message: "No stream key found in request!" },
|
|
||||||
{ status: StatusCodes.BAD_REQUEST }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await req.formData();
|
|
||||||
const streamKey = data.get("name")?.toString();
|
|
||||||
if (!streamKey) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ message: "No stream key found in request!" },
|
|
||||||
{ status: StatusCodes.BAD_REQUEST }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const results = await db
|
|
||||||
.selectDistinct()
|
|
||||||
.from(users)
|
|
||||||
.where(eq(users.streamKey, streamKey));
|
|
||||||
const user = results[0];
|
|
||||||
if (!user) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ message: "Unauthorised!" },
|
|
||||||
{ status: StatusCodes.UNAUTHORIZED }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const showResults = await db
|
|
||||||
.selectDistinct()
|
|
||||||
.from(liveShows)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(liveShows.userId, user.id),
|
|
||||||
or(eq(liveShows.status, "AWAITING"), eq(liveShows.status, "STREAMING"))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
const show = showResults[0];
|
|
||||||
if (!show) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ message: "No in progress show found for user!" },
|
|
||||||
{ status: StatusCodes.BAD_REQUEST }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await waitForShowQueue.enqueue(show.id, { delay: 1 });
|
|
||||||
return new NextResponse("Redirecting.....", {
|
|
||||||
status: StatusCodes.MOVED_TEMPORARILY,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "text/plain",
|
|
||||||
Location: show.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
import { liveShows } from "@/db/schema";
|
|
||||||
import { ShowStatus } from "@/lib/models";
|
|
||||||
import { createPusherServer } from "@/lib/services/realtime";
|
|
||||||
import { showRouter } from "@/server/api/routers/show";
|
|
||||||
import { userRouter } from "@/server/api/routers/user";
|
|
||||||
import { createTRPCContext } from "@/server/api/trpc";
|
|
||||||
import { db } from "@/server/db";
|
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
import { StatusCodes } from "http-status-codes";
|
|
||||||
import { type NextRequest, NextResponse } from "next/server";
|
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
|
||||||
// Process a POST request
|
|
||||||
// const ctx = await createTRPCContext({ req, res });
|
|
||||||
// const userApi = userRouter.createCaller(ctx);
|
|
||||||
// const showApi = showRouter.createCaller(ctx);
|
|
||||||
|
|
||||||
// const { name: streamKey } = req.body as { name: string };
|
|
||||||
// if (!streamKey) {
|
|
||||||
// return new NextResponse(
|
|
||||||
// JSON.stringify({ message: "No stream key found in request!" }),
|
|
||||||
// { status: StatusCodes.BAD_REQUEST },
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// const user = await userApi.getByStreamKey({ streamKey });
|
|
||||||
|
|
||||||
// if (!user) {
|
|
||||||
// return new NextResponse(JSON.stringify({ message: "Unauthorised!" }), {
|
|
||||||
// status: StatusCodes.UNAUTHORIZED,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// const show = await showApi.checkForInProgress({ userId: user.id });
|
|
||||||
|
|
||||||
// if (!show) {
|
|
||||||
// return new NextResponse(
|
|
||||||
// JSON.stringify({ message: "No in progress show found for user!!" }),
|
|
||||||
// {
|
|
||||||
// status: StatusCodes.BAD_REQUEST,
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// //show go bye bye
|
|
||||||
// /*
|
|
||||||
// await scheduler.TriggerJob(new JobKey("SaveLiveShowJob", "DEFAULT"), new JobDataMap(
|
|
||||||
// new Dictionary<string, string> {
|
|
||||||
// {"ShowId", show.Id.ToString()}
|
|
||||||
// }
|
|
||||||
// ));
|
|
||||||
// */
|
|
||||||
// show.status = ShowStatus.ended;
|
|
||||||
// await db
|
|
||||||
// .update(liveShows)
|
|
||||||
// .set({ ...show, status: "FINISHED" })
|
|
||||||
// .where(eq(liveShows.id, show.id));
|
|
||||||
|
|
||||||
// await createPusherServer().trigger(`ls_${show.id}`, "show-finished", show);
|
|
||||||
// return new NextResponse(JSON.stringify(show), {
|
|
||||||
return new NextResponse(JSON.stringify({}), {
|
|
||||||
status: StatusCodes.OK,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { NextResponse } from "next/server";
|
|
||||||
|
|
||||||
export function GET() {
|
|
||||||
return NextResponse.json({ ping: "pong" });
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
import { liveShows, users } from "@/db/schema";
|
|
||||||
import { createPusherServer } from "@/lib/services/realtime";
|
|
||||||
import { mapShowToShowModel } from "@/lib/utils/mappers/showMappers";
|
|
||||||
import { mapDbAuthUserToUserModel } from "@/lib/utils/mappers/userMapper";
|
|
||||||
import { db } from "@/server/db";
|
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
import { StatusCodes } from "http-status-codes";
|
|
||||||
import { Queue } from "quirrel/next-app";
|
|
||||||
import superagent from "superagent";
|
|
||||||
|
|
||||||
export const waitForShowQueue = Queue(
|
|
||||||
"api/queues/shows/wait", // 👈 the route it's reachable on
|
|
||||||
async (showId: string) => {
|
|
||||||
const rt = createPusherServer();
|
|
||||||
const url = `https://live-mixyboos.dev.fergl.ie:9091/hls/${showId}/index.m3u8`;
|
|
||||||
console.log("wait", "Checking URL", url);
|
|
||||||
const results = await db
|
|
||||||
.selectDistinct()
|
|
||||||
.from(liveShows)
|
|
||||||
.where(eq(liveShows.id, showId));
|
|
||||||
const show = results[0];
|
|
||||||
if (!show) {
|
|
||||||
throw new Error(`Unable to find show ${showId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await db
|
|
||||||
.selectDistinct()
|
|
||||||
.from(users)
|
|
||||||
.where(eq(users.id, show.userId));
|
|
||||||
|
|
||||||
if (!result || !result[0]) {
|
|
||||||
throw new Error(`Unable to find user ${show.userId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = mapDbAuthUserToUserModel(result[0]);
|
|
||||||
if (!user) {
|
|
||||||
throw new Error(`Unable to find user ${show.userId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await superagent
|
|
||||||
.get(url)
|
|
||||||
.retry(10, (err, res) => {
|
|
||||||
console.log("wait", "result", res.notFound, res.statusCode, res.status);
|
|
||||||
const shouldRetry = res.notFound;
|
|
||||||
if (shouldRetry) {
|
|
||||||
console.log("wait", "Waiting to retry");
|
|
||||||
Atomics.wait(
|
|
||||||
new Int32Array(new SharedArrayBuffer(4)),
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
2 * 1000,
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return res.notFound;
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.log("WaitForShow", "Error fetching retrying", err);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("wait", "Finished waiting for show", res?.statusCode);
|
|
||||||
if (res?.statusCode === StatusCodes.OK) {
|
|
||||||
//showtime
|
|
||||||
|
|
||||||
//update mix entry in db
|
|
||||||
await db
|
|
||||||
.update(liveShows)
|
|
||||||
.set({
|
|
||||||
...show,
|
|
||||||
status: "STREAMING",
|
|
||||||
})
|
|
||||||
.where(eq(liveShows.id, show.id));
|
|
||||||
|
|
||||||
//push status to client
|
|
||||||
await rt.trigger(`ls_${showId}`, "show-started", {
|
|
||||||
show: mapShowToShowModel(show, user),
|
|
||||||
});
|
|
||||||
console.log("waitForShow", "show-started", showId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//notime
|
|
||||||
await rt.trigger(`ls_${showId}`, "show-failure", {
|
|
||||||
id: showId,
|
|
||||||
});
|
|
||||||
console.error("waitForShow", "FAILED: show-failure", showId);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
export const POST = waitForShowQueue;
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import { spawn } from "child_process";
|
|
||||||
import fs from "fs";
|
|
||||||
import os from "os";
|
|
||||||
import path from "path";
|
|
||||||
import { uploadFolder } from "@/lib/services/azure/serverUploader";
|
|
||||||
import { Queue } from "quirrel/next-app";
|
|
||||||
import { db } from "@/server/db";
|
|
||||||
import { mixes } from "@/db/schema";
|
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
import * as Sentry from "@sentry/nextjs";
|
|
||||||
|
|
||||||
export const processMixQueue = Queue(
|
|
||||||
"api/queues/upload/mix", // 👈 the route it's reachable on
|
|
||||||
async (job: { filePath: string; mixId: string }) => {
|
|
||||||
const { filePath, mixId } = job;
|
|
||||||
const outputDir = `${os.tmpdir()}/podnoms/${mixId}`;
|
|
||||||
fs.mkdirSync(outputDir, {
|
|
||||||
recursive: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
const process = spawn("ffmpeg", [
|
|
||||||
"-i",
|
|
||||||
filePath,
|
|
||||||
"-codec:", "copy",
|
|
||||||
"-start_number", "0",
|
|
||||||
"-hls_time", "10",
|
|
||||||
"-hls_list_size", "0",
|
|
||||||
"-f", "hls",
|
|
||||||
`${outputDir}/${mixId}.m3u8`,
|
|
||||||
]);
|
|
||||||
|
|
||||||
process.stdout.on("data", (data) => {
|
|
||||||
console.log("processMixQueue", "stdout", data);
|
|
||||||
});
|
|
||||||
|
|
||||||
process.stderr.on("data", (data) => {
|
|
||||||
console.log("processMixQueue", "stderr", data);
|
|
||||||
});
|
|
||||||
|
|
||||||
process.on("close", (code) => {
|
|
||||||
console.log("processMixQueue", "close", code);
|
|
||||||
if (code !== 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadFolder(outputDir, "audio", path.join("mixes", mixId))
|
|
||||||
.then(async (r) => {
|
|
||||||
if (r) {
|
|
||||||
//if client has saved mix then we are processed
|
|
||||||
//if not client will check for file and set processed
|
|
||||||
await db
|
|
||||||
.update(mixes)
|
|
||||||
.set({ isProcessed: true })
|
|
||||||
.where(eq(mixes.id, mixId));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.log("route", "error uploading output folder", err);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
export const POST = processMixQueue;
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import { writeFile } from "fs/promises";
|
|
||||||
import { getFileExtension } from "@/lib/utils/fileUtils";
|
|
||||||
import { NextResponse, type NextRequest } from "next/server";
|
|
||||||
import { processMixQueue } from "../queues/upload/mix/route";
|
|
||||||
import * as Sentry from "@sentry/nextjs";
|
|
||||||
import path from "path";
|
|
||||||
import os from "os";
|
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
|
||||||
const data = await req.formData();
|
|
||||||
const file: File | null = data.get("file") as unknown as File;
|
|
||||||
const id: string | null = data.get("mixId") as unknown as string;
|
|
||||||
|
|
||||||
if (!file) {
|
|
||||||
return NextResponse.json({ success: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
const bytes = await file.arrayBuffer();
|
|
||||||
const buffer = Buffer.from(bytes);
|
|
||||||
|
|
||||||
const outputFile = path.join(
|
|
||||||
os.tmpdir(),
|
|
||||||
`${id}.${getFileExtension(file.name)}`
|
|
||||||
);
|
|
||||||
await writeFile(outputFile, buffer);
|
|
||||||
console.log(`open ${outputFile} to see the uploaded file`);
|
|
||||||
try {
|
|
||||||
const result = await processMixQueue.enqueue(
|
|
||||||
{ filePath: outputFile, mixId: id },
|
|
||||||
{ delay: 1 }
|
|
||||||
);
|
|
||||||
console.log('route', 'processMixSendQueueResult', result);
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.captureException(err);
|
|
||||||
}
|
|
||||||
return NextResponse.json({ success: true });
|
|
||||||
}
|
|
||||||
@@ -29,6 +29,7 @@ import Link from "next/link";
|
|||||||
import { signIn } from "next-auth/react";
|
import { signIn } from "next-auth/react";
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||||
|
import logger from "@/lib/logger";
|
||||||
|
|
||||||
const LoginPage = () => {
|
const LoginPage = () => {
|
||||||
const [loginError, setLoginError] = React.useState(false);
|
const [loginError, setLoginError] = React.useState(false);
|
||||||
@@ -45,10 +46,10 @@ const LoginPage = () => {
|
|||||||
function onSubmit(values: z.infer<typeof formSchema>) {
|
function onSubmit(values: z.infer<typeof formSchema>) {
|
||||||
// Do something with the form values.
|
// Do something with the form values.
|
||||||
// ✅ This will be type-safe and validated.
|
// ✅ This will be type-safe and validated.
|
||||||
console.log(values);
|
logger.debug(values);
|
||||||
setLoginError(false);
|
setLoginError(false);
|
||||||
signIn("credentials", {
|
signIn("credentials", {
|
||||||
email: values.usernameOrEmail,
|
username: values.usernameOrEmail,
|
||||||
password: values.password,
|
password: values.password,
|
||||||
callbackUrl:
|
callbackUrl:
|
||||||
searchParams?.get("callbackUrl") ||
|
searchParams?.get("callbackUrl") ||
|
||||||
@@ -57,14 +58,16 @@ const LoginPage = () => {
|
|||||||
redirect: false,
|
redirect: false,
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
if (result?.ok) {
|
//TODO: have to check result?.error rather than result.ok
|
||||||
|
//TODO: https://github.com/nextauthjs/next-auth/issues/7725#issuecomment-1649310412
|
||||||
|
if (!result?.error) {
|
||||||
router.push(searchParams?.get("returnUrl") || "/");
|
router.push(searchParams?.get("returnUrl") || "/");
|
||||||
} else {
|
} else {
|
||||||
setLoginError(true);
|
setLoginError(true);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error("login", "handleLogin", err);
|
logger.error("login", "handleLogin", err);
|
||||||
setLoginError(true);
|
setLoginError(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -109,6 +112,13 @@ const LoginPage = () => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="border-sm mt-4 text-center shadow-lg">or</div>
|
<div className="border-sm mt-4 text-center shadow-lg">or</div>
|
||||||
|
{loginError && (
|
||||||
|
<Alert variant="destructive">
|
||||||
|
<Icons.error className="h-4 w-4" />
|
||||||
|
<AlertTitle>Error</AlertTitle>
|
||||||
|
<AlertDescription>Unable to sign you in</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||||||
<FormField
|
<FormField
|
||||||
@@ -155,15 +165,7 @@ const LoginPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{loginError && (
|
|
||||||
<div>
|
|
||||||
<Alert variant="destructive">
|
|
||||||
<Icons.error className="h-4 w-4" />
|
|
||||||
<AlertTitle>Error</AlertTitle>
|
|
||||||
<AlertDescription>Unable to sign you in</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="text-sm font-medium text-gray-500">
|
<div className="text-sm font-medium text-gray-500">
|
||||||
Not registered?
|
Not registered?
|
||||||
<Link
|
<Link
|
||||||
|
|||||||
@@ -19,9 +19,12 @@ import { Icons } from "@/components/icons";
|
|||||||
import { notice } from "@/lib/components/notifications/toast";
|
import { notice } from "@/lib/components/notifications/toast";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { signIn } from "next-auth/react";
|
import { signIn } from "next-auth/react";
|
||||||
import { api } from "@/lib/utils/api";
|
import AuthService from "@/lib/services/api/auth-service";
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||||
|
import logger from "@/lib/logger";
|
||||||
|
|
||||||
const LoginPage = () => {
|
const LoginPage = () => {
|
||||||
|
const [hasErrors, setHasErrors] = React.useState(false);
|
||||||
const schema = z
|
const schema = z
|
||||||
.object({
|
.object({
|
||||||
username: z
|
username: z
|
||||||
@@ -42,33 +45,35 @@ const LoginPage = () => {
|
|||||||
const form = useForm<z.infer<typeof schema>>({
|
const form = useForm<z.infer<typeof schema>>({
|
||||||
resolver: zodResolver(schema),
|
resolver: zodResolver(schema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
username: "",
|
username: "superawesomedjperson",
|
||||||
email: "",
|
email: "fergal.moran+mixyboos@gmail.com",
|
||||||
password: "",
|
password: "secret",
|
||||||
},
|
confirmPassword: "secret",
|
||||||
});
|
|
||||||
const register = api.auth.signUp.useMutation({
|
|
||||||
onSuccess: (result) => {
|
|
||||||
console.log("page", "register_success", result);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
async function onSubmit(values: z.infer<typeof schema>) {
|
async function onSubmit(values: z.infer<typeof schema>) {
|
||||||
// Do something with the form values.
|
logger.debug(values);
|
||||||
// ✅ This will be type-safe and validated.
|
setHasErrors(false);
|
||||||
console.log(values);
|
|
||||||
try {
|
try {
|
||||||
const result = await register.mutateAsync({
|
const result = await new AuthService().registerUser(
|
||||||
email: values.email,
|
values.email,
|
||||||
username: values.username,
|
values.password,
|
||||||
password: values.password,
|
values.confirmPassword,
|
||||||
});
|
values.username,
|
||||||
console.log("page", "handleRegister", result);
|
);
|
||||||
if (result?.status === 201) {
|
if (result) {
|
||||||
await signIn();
|
const response = await signIn("credentials", {
|
||||||
|
username: values.username,
|
||||||
|
password: values.password,
|
||||||
|
callbackUrl: "/",
|
||||||
|
redirect: true,
|
||||||
|
});
|
||||||
|
logger.debug("RegisterPage", "signin_credentials", response);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("RegisterPage", "handleLogin", err);
|
logger.error("RegisterPage", "handleLogin", err);
|
||||||
|
setHasErrors(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
@@ -114,6 +119,22 @@ const LoginPage = () => {
|
|||||||
<div className="border-sm mt-4 text-center shadow-lg">
|
<div className="border-sm mt-4 text-center shadow-lg">
|
||||||
or create a new account
|
or create a new account
|
||||||
</div>
|
</div>
|
||||||
|
{hasErrors && (
|
||||||
|
<Alert variant="destructive">
|
||||||
|
<Icons.error className="h-4 w-4" />
|
||||||
|
<AlertTitle>Yikes! Something went wrong...</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
Please try again, or
|
||||||
|
<Link
|
||||||
|
href="/auth/login"
|
||||||
|
className="ml-2 text-fuchsia-600 hover:underline"
|
||||||
|
>
|
||||||
|
login{" "}
|
||||||
|
</Link>
|
||||||
|
if you already have an account?
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||||||
<FormField
|
<FormField
|
||||||
|
|||||||
@@ -1,35 +1,10 @@
|
|||||||
"use client";
|
import Session from "@/lib/components/debug/Session";
|
||||||
|
import { getServerSession } from "next-auth";
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardFooter,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@/components/ui/card";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import { Switch } from "@headlessui/react";
|
|
||||||
import { BellRing, Check } from "lucide-react";
|
|
||||||
import { useSession } from "next-auth/react";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
const IndexPage = () => {
|
const IndexPage = () => {
|
||||||
const session = useSession();
|
const session = getServerSession();
|
||||||
return (
|
return <Session serverSession={session} />;
|
||||||
<div className="container mt-10 flex w-3/4">
|
|
||||||
<Card className="w-full">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Session</CardTitle>
|
|
||||||
<CardDescription>Client side session.</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="grid gap-4 text-muted">
|
|
||||||
{JSON.stringify(session, null, " ")}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default IndexPage;
|
export default IndexPage;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import HeroPage from "@/lib/components/pages/HeroPage";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { getServerSession } from "next-auth";
|
import { getServerSession } from "next-auth";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { authOptions } from "@/lib/services/api/config";
|
import { authOptions } from "@/lib/services/auth/config";
|
||||||
|
|
||||||
const Home = async () => {
|
const Home = async () => {
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession(authOptions);
|
||||||
|
|||||||
@@ -1,80 +1,88 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Bar, BarChart, ResponsiveContainer, XAxis, YAxis } from "recharts";
|
import React from "react";
|
||||||
|
|
||||||
const data = [
|
const PlaysGraph: React.FC = () => {
|
||||||
{
|
return <div>PlaysGraph</div>;
|
||||||
name: "Jan",
|
|
||||||
total: Math.floor(Math.random() * 5000) + 1000,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Feb",
|
|
||||||
total: Math.floor(Math.random() * 5000) + 1000,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Mar",
|
|
||||||
total: Math.floor(Math.random() * 5000) + 1000,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Apr",
|
|
||||||
total: Math.floor(Math.random() * 5000) + 1000,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "May",
|
|
||||||
total: Math.floor(Math.random() * 5000) + 1000,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Jun",
|
|
||||||
total: Math.floor(Math.random() * 5000) + 1000,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Jul",
|
|
||||||
total: Math.floor(Math.random() * 5000) + 1000,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Aug",
|
|
||||||
total: Math.floor(Math.random() * 5000) + 1000,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Sep",
|
|
||||||
total: Math.floor(Math.random() * 5000) + 1000,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Oct",
|
|
||||||
total: Math.floor(Math.random() * 5000) + 1000,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Nov",
|
|
||||||
total: Math.floor(Math.random() * 5000) + 1000,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Dec",
|
|
||||||
total: Math.floor(Math.random() * 5000) + 1000,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const PlaysGraph = () => {
|
|
||||||
return (
|
|
||||||
<ResponsiveContainer width="100%" height={350}>
|
|
||||||
<BarChart data={data}>
|
|
||||||
<XAxis
|
|
||||||
dataKey="name"
|
|
||||||
stroke="#888888"
|
|
||||||
fontSize={12}
|
|
||||||
tickLine={false}
|
|
||||||
axisLine={false}
|
|
||||||
/>
|
|
||||||
<YAxis
|
|
||||||
stroke="#888888"
|
|
||||||
fontSize={12}
|
|
||||||
tickLine={false}
|
|
||||||
axisLine={false}
|
|
||||||
tickFormatter={(value) => `$${value as string}`}
|
|
||||||
/>
|
|
||||||
<Bar dataKey="total" fill="#adfa1d" radius={[4, 4, 0, 0]} />
|
|
||||||
</BarChart>
|
|
||||||
</ResponsiveContainer>
|
|
||||||
);z
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PlaysGraph;
|
export default PlaysGraph;
|
||||||
|
|
||||||
|
// import { Bar, BarChart, ResponsiveContainer, XAxis, YAxis } from "recharts";
|
||||||
|
|
||||||
|
// const data = [
|
||||||
|
// {
|
||||||
|
// name: "Jan",
|
||||||
|
// total: Math.floor(Math.random() * 5000) + 1000,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "Feb",
|
||||||
|
// total: Math.floor(Math.random() * 5000) + 1000,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "Mar",
|
||||||
|
// total: Math.floor(Math.random() * 5000) + 1000,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "Apr",
|
||||||
|
// total: Math.floor(Math.random() * 5000) + 1000,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "May",
|
||||||
|
// total: Math.floor(Math.random() * 5000) + 1000,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "Jun",
|
||||||
|
// total: Math.floor(Math.random() * 5000) + 1000,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "Jul",
|
||||||
|
// total: Math.floor(Math.random() * 5000) + 1000,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "Aug",
|
||||||
|
// total: Math.floor(Math.random() * 5000) + 1000,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "Sep",
|
||||||
|
// total: Math.floor(Math.random() * 5000) + 1000,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "Oct",
|
||||||
|
// total: Math.floor(Math.random() * 5000) + 1000,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "Nov",
|
||||||
|
// total: Math.floor(Math.random() * 5000) + 1000,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "Dec",
|
||||||
|
// total: Math.floor(Math.random() * 5000) + 1000,
|
||||||
|
// },
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// const PlaysGraph = () => {
|
||||||
|
// return (
|
||||||
|
// <ResponsiveContainer width="100%" height={350}>
|
||||||
|
// <BarChart data={data}>
|
||||||
|
// <XAxis
|
||||||
|
// dataKey="name"
|
||||||
|
// stroke="#888888"
|
||||||
|
// fontSize={12}
|
||||||
|
// tickLine={false}
|
||||||
|
// axisLine={false}
|
||||||
|
// />
|
||||||
|
// <YAxis
|
||||||
|
// stroke="#888888"
|
||||||
|
// fontSize={12}
|
||||||
|
// tickLine={false}
|
||||||
|
// axisLine={false}
|
||||||
|
// tickFormatter={(value) => `$${value as string}`}
|
||||||
|
// />
|
||||||
|
// <Bar dataKey="total" fill="#adfa1d" radius={[4, 4, 0, 0]} />
|
||||||
|
// </BarChart>
|
||||||
|
// </ResponsiveContainer>
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
|
// export default PlaysGraph;
|
||||||
|
|||||||
23
src/lib/components/debug/Session.tsx
Normal file
23
src/lib/components/debug/Session.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
"use client";
|
||||||
|
import React from "react";
|
||||||
|
import SessionPrinter from "./SessionPrinter";
|
||||||
|
import { useSession } from "next-auth/react";
|
||||||
|
|
||||||
|
type SessionProps = {
|
||||||
|
serverSession: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Session: React.FC<SessionProps> = ({ serverSession }) => {
|
||||||
|
const session = useSession();
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SessionPrinter
|
||||||
|
session={serverSession}
|
||||||
|
sessionType={"Server side session."}
|
||||||
|
/>
|
||||||
|
<SessionPrinter session={session} sessionType={"Client side session."} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Session;
|
||||||
34
src/lib/components/debug/SessionPrinter.tsx
Normal file
34
src/lib/components/debug/SessionPrinter.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import React from "react";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
|
||||||
|
type SessionPrinterProps = {
|
||||||
|
sessionType: string;
|
||||||
|
session: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SessionPrinter: React.FC<SessionPrinterProps> = ({
|
||||||
|
sessionType,
|
||||||
|
session,
|
||||||
|
}: SessionPrinterProps) => {
|
||||||
|
return (
|
||||||
|
<div className="container mt-10 flex w-3/4">
|
||||||
|
<Card className="w-full">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Session</CardTitle>
|
||||||
|
<CardDescription>{sessionType}</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="text-muted grid gap-4">
|
||||||
|
{JSON.stringify(session, null, " ")}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SessionPrinter;
|
||||||
@@ -1,31 +1,14 @@
|
|||||||
import pino from "pino";
|
import pino from "pino";
|
||||||
import { logflarePinoVercel } from "pino-logflare";
|
|
||||||
|
|
||||||
// create pino-logflare console stream for serverless functions and send function for browser logs
|
const mixin = {
|
||||||
// Browser logs are going to: https://logflare.app/sources/13989
|
appName: "MixyBoos",
|
||||||
// Vercel log drain was setup to send logs here: https://logflare.app/sources/13830
|
target: 'pino-pretty'
|
||||||
|
};
|
||||||
const { stream, send } = logflarePinoVercel({
|
// create pino logger
|
||||||
apiKey: "MK3qgU_-pwHQ",
|
const logger = pino({
|
||||||
sourceToken: "2c94c62f-012d-4891-8ef5-38103089e4af",
|
mixin() {
|
||||||
|
return mixin;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// create pino logger
|
|
||||||
const logger = pino(
|
|
||||||
{
|
|
||||||
browser: {
|
|
||||||
transmit: {
|
|
||||||
level: "info",
|
|
||||||
send,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
level: "info",
|
|
||||||
base: {
|
|
||||||
env: process.env.NODE_ENV,
|
|
||||||
revision: process.env.VERCEL_GITHUB_COMMIT_SHA,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
stream,
|
|
||||||
);
|
|
||||||
|
|
||||||
export default logger;
|
export default logger;
|
||||||
|
|||||||
@@ -5,5 +5,12 @@ type AuthTokenModel = {
|
|||||||
expires_in: number;
|
expires_in: number;
|
||||||
token_type: string;
|
token_type: string;
|
||||||
expiration_date: string;
|
expiration_date: string;
|
||||||
|
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
name: string;
|
||||||
|
displayName: string;
|
||||||
|
slug: string;
|
||||||
|
profileImage: string;
|
||||||
};
|
};
|
||||||
export default AuthTokenModel;
|
export default AuthTokenModel;
|
||||||
|
|||||||
@@ -35,9 +35,7 @@ class ApiService {
|
|||||||
const session = await getSession();
|
const session = await getSession();
|
||||||
if (session || this._token) {
|
if (session || this._token) {
|
||||||
config.headers = {
|
config.headers = {
|
||||||
Authorization: `Bearer ${
|
Authorization: `Bearer ${session ? session.accessToken : this._token}`,
|
||||||
session ? session.user.accessToken : this._token
|
|
||||||
}`,
|
|
||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { AxiosError } from "axios";
|
import { AxiosError } from "axios";
|
||||||
import ApiService from "./api-service";
|
import ApiService from "./api-service";
|
||||||
import { type AuthTokenModel, UserModel } from "@/lib/models";
|
import type { AuthTokenModel, UserModel } from "@/lib/models";
|
||||||
import logger from "@/lib/logger";
|
import logger from "@/lib/logger";
|
||||||
|
|
||||||
class AuthService extends ApiService {
|
class AuthService extends ApiService {
|
||||||
@@ -78,15 +78,15 @@ class AuthService extends ApiService {
|
|||||||
return Promise.reject("Unable to log in");
|
return Promise.reject("Unable to log in");
|
||||||
};
|
};
|
||||||
registerUser = async (
|
registerUser = async (
|
||||||
userName: string,
|
email: string,
|
||||||
password: string,
|
password: string,
|
||||||
confirmPassword: string,
|
confirmPassword: string,
|
||||||
displayName: string,
|
username: string = "",
|
||||||
): Promise<boolean> => {
|
): Promise<boolean> => {
|
||||||
const url = "/account/register";
|
const url = "/account/register";
|
||||||
const result = await this._client.post(
|
const result = await this._client.post(
|
||||||
url,
|
url,
|
||||||
{ userName, password, confirmPassword, displayName },
|
{ username: email, password, confirmPassword, displayName: username },
|
||||||
this.jsonConfig,
|
this.jsonConfig,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -97,6 +97,7 @@ class AuthService extends ApiService {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
loginUser = async (
|
loginUser = async (
|
||||||
username: string,
|
username: string,
|
||||||
password: string,
|
password: string,
|
||||||
@@ -106,7 +107,6 @@ class AuthService extends ApiService {
|
|||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
params.append("username", username);
|
params.append("username", username);
|
||||||
params.append("password", password);
|
params.append("password", password);
|
||||||
UserModel;
|
|
||||||
params.append("grant_type", process.env.AUTH_GRANT_TYPE as string);
|
params.append("grant_type", process.env.AUTH_GRANT_TYPE as string);
|
||||||
params.append("scope", process.env.AUTH_SCOPE as string);
|
params.append("scope", process.env.AUTH_SCOPE as string);
|
||||||
params.append("client_id", process.env.AUTH_CLIENT_ID as string);
|
params.append("client_id", process.env.AUTH_CLIENT_ID as string);
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
import logger from "@/lib/logger";
|
|
||||||
import { type AuthOptions } from "next-auth";
|
|
||||||
import jwt_decode, { type JwtPayload } from "jwt-decode";
|
|
||||||
import CredentialsProvider from "next-auth/providers/credentials";
|
|
||||||
import AuthService from "./auth-service";
|
|
||||||
import { type AuthTokenModel } from "@/lib/models";
|
|
||||||
import AspNetIdentityAdapter from "../auth/asp-net-identity-adapter";
|
|
||||||
|
|
||||||
export const authOptions: AuthOptions = {
|
|
||||||
adapter: AspNetIdentityAdapter(process.env.NEXT_PUBLIC_API_URL as string),
|
|
||||||
providers: [
|
|
||||||
CredentialsProvider({
|
|
||||||
name: "Email and Password",
|
|
||||||
credentials: {
|
|
||||||
userName: {
|
|
||||||
label: "Username",
|
|
||||||
type: "text",
|
|
||||||
placeholder: "Username or email address",
|
|
||||||
},
|
|
||||||
password: {
|
|
||||||
label: "Password",
|
|
||||||
type: "password",
|
|
||||||
placeholder: "Password",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
authorize: async (credentials, _req): Promise<any> => {
|
|
||||||
logger.info({ authorize: "Authorizing" });
|
|
||||||
try {
|
|
||||||
if (!credentials) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const authService = new AuthService();
|
|
||||||
const token = await authService.getAuthToken(
|
|
||||||
credentials.userName,
|
|
||||||
credentials.password,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!token) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const decodedToken = jwt_decode<JwtPayload & AuthTokenModel>(
|
|
||||||
token.access_token,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (decodedToken) {
|
|
||||||
const profile = {
|
|
||||||
id: decodedToken.sub,
|
|
||||||
// name: decodedToken.name,
|
|
||||||
// displayName: decodedToken.displayName,
|
|
||||||
// email: decodedToken.email,
|
|
||||||
// profileImage: decodedToken.profileImage,
|
|
||||||
// slug: decodedToken.slug,
|
|
||||||
accessToken: token.access_token,
|
|
||||||
accessTokenExpires: token.expires_in,
|
|
||||||
};
|
|
||||||
return profile;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(`Error authorizing: ${err}`);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
143
src/lib/services/api/mix-service.ts
Normal file
143
src/lib/services/api/mix-service.ts
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
import { AxiosError } from "axios";
|
||||||
|
import ApiService from "./api-service";
|
||||||
|
import { type MixModel } from "@/lib/models";
|
||||||
|
|
||||||
|
class MixService extends ApiService {
|
||||||
|
getMixes = async (): Promise<Array<MixModel>> => {
|
||||||
|
try {
|
||||||
|
const result = await this._client.get("/mix");
|
||||||
|
if (result?.status === 200) {
|
||||||
|
return result.data;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log("authService", "getMixes_error", err);
|
||||||
|
if (err instanceof AxiosError) {
|
||||||
|
if (![401, 400].includes(err.status as number))
|
||||||
|
throw new Error(err as any);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error("Unable to load mixes");
|
||||||
|
};
|
||||||
|
|
||||||
|
getMixesFeed = async (): Promise<MixModel[]> => {
|
||||||
|
try {
|
||||||
|
const result = await this._client.get("/mix/feed");
|
||||||
|
if (result?.status === 200) {
|
||||||
|
return result.data;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log("authService", "getMixes_error", err);
|
||||||
|
if (err instanceof AxiosError) {
|
||||||
|
if (![401, 400].includes(err.status as number))
|
||||||
|
throw new Error(err as any);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error("Unable to load mixes");
|
||||||
|
};
|
||||||
|
getUserMixes = async (user: string): Promise<MixModel[]> => {
|
||||||
|
try {
|
||||||
|
const result = await this._client.get(`/mix/user?user=${user}`);
|
||||||
|
if (result?.status === 200) {
|
||||||
|
return result.data;
|
||||||
|
}
|
||||||
|
if (result?.status === 204) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log("authService", "getMixes_error", err);
|
||||||
|
if (err instanceof AxiosError) {
|
||||||
|
if (![401, 400].includes(err.status as number))
|
||||||
|
throw new Error(err as any);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error("Unable to load mixes");
|
||||||
|
};
|
||||||
|
|
||||||
|
getByUserAndSlug = async (
|
||||||
|
userSlug: string,
|
||||||
|
mixSlug: string,
|
||||||
|
): Promise<MixModel> => {
|
||||||
|
try {
|
||||||
|
const result = await this._client.get(
|
||||||
|
`/mix/single?user=${userSlug}&mix=${mixSlug}`,
|
||||||
|
);
|
||||||
|
if (result?.status === 200) {
|
||||||
|
return result.data;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log("authService", "getMixes_error", err);
|
||||||
|
if (err instanceof AxiosError) {
|
||||||
|
if (![401, 400].includes(err.status as number))
|
||||||
|
throw new Error(err as any);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error("Unable to load mixes");
|
||||||
|
};
|
||||||
|
|
||||||
|
createMix = async (mix: MixModel): Promise<MixModel> => {
|
||||||
|
try {
|
||||||
|
const result = await this._client.post("/mix", mix);
|
||||||
|
if (result?.status === 201) {
|
||||||
|
return result.data;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log("authService", "getUser_error", err);
|
||||||
|
if (err instanceof AxiosError) {
|
||||||
|
if (![401, 400].includes(err.status as number))
|
||||||
|
throw new Error(err as any);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error("Unable to create mix");
|
||||||
|
};
|
||||||
|
|
||||||
|
updateMix = async (mix: MixModel): Promise<MixModel> => {
|
||||||
|
try {
|
||||||
|
const result = await this._client.patch("/mix", mix);
|
||||||
|
if (result?.status === 201) {
|
||||||
|
return result.data;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log("authService", "getUser_error", err);
|
||||||
|
if (err instanceof AxiosError) {
|
||||||
|
if (![401, 400].includes(err.status as number)) {
|
||||||
|
throw new Error(err as any);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error("Unable to create mix");
|
||||||
|
};
|
||||||
|
addLike = async (mix: MixModel): Promise<boolean> => {
|
||||||
|
const result = await this._client.post(`/mix/addlike?mixId=${mix.id}`);
|
||||||
|
return result.status === 200;
|
||||||
|
};
|
||||||
|
deleteMix = async (mix: MixModel): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
const result = await this._client.delete(`/mix?id=${mix.id}`);
|
||||||
|
return result.status === 200;
|
||||||
|
} catch (err) {
|
||||||
|
console.log("authService", "getUser_error", err);
|
||||||
|
if (err instanceof AxiosError) {
|
||||||
|
if (![401, 400].includes(err.status as number)) {
|
||||||
|
throw new Error(err as any);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error("Unable to create mix");
|
||||||
|
};
|
||||||
|
getMixAudioUrl = async (mix: MixModel): Promise<string> => {
|
||||||
|
try {
|
||||||
|
const result = await this._client.get(`/mix/audiourl?id=${mix.id}`);
|
||||||
|
return result.data;
|
||||||
|
} catch (err) {
|
||||||
|
console.log("authService", "getUser_error", err);
|
||||||
|
if (err instanceof AxiosError) {
|
||||||
|
if (![401, 400].includes(err.status as number)) {
|
||||||
|
throw new Error(err as any);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error("Unable to get mix audio url");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MixService;
|
||||||
54
src/lib/services/api/profile-service.ts
Normal file
54
src/lib/services/api/profile-service.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import type { UserModel } from "@/lib/models";
|
||||||
|
import ApiService from "./api-service";
|
||||||
|
import logger from "@/lib/logger";
|
||||||
|
|
||||||
|
class ProfileService extends ApiService {
|
||||||
|
/**
|
||||||
|
* Get the currently logged in user's profile
|
||||||
|
*/
|
||||||
|
getProfile = async (): Promise<UserModel | undefined> => {
|
||||||
|
try {
|
||||||
|
const results = await this._client.get("/profile/me");
|
||||||
|
if (results.status === 200) {
|
||||||
|
return results.data;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.error("profile-service.ts", "Unable to get user's profile.", err);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
getProfileBySlug = async (slug: string): Promise<UserModel | undefined> => {
|
||||||
|
try {
|
||||||
|
const results = await this._client.get(`/profile?slug=${slug}`);
|
||||||
|
if (results.status === 200) {
|
||||||
|
return results.data;
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
toggleFollow = async (slug: string): Promise<boolean> => {
|
||||||
|
const result = await this._client.post(
|
||||||
|
`/profile/togglefollow?slug=${slug}`,
|
||||||
|
);
|
||||||
|
return result.status === 200;
|
||||||
|
};
|
||||||
|
|
||||||
|
updateProfile = async (
|
||||||
|
profile: UserModel,
|
||||||
|
): Promise<UserModel | undefined> => {
|
||||||
|
try {
|
||||||
|
const result = await this._client.post(
|
||||||
|
`/profile`,
|
||||||
|
profile,
|
||||||
|
this.jsonConfig,
|
||||||
|
);
|
||||||
|
|
||||||
|
return result.data as UserModel;
|
||||||
|
} catch (err) {}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProfileService;
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import mixService from "./mix-service";
|
|
||||||
|
|
||||||
export { mixService };
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { db } from "@/server/db";
|
|
||||||
|
|
||||||
const mixService = {
|
|
||||||
getMixAudioUrl: async (mixId: string) => {
|
|
||||||
// const mixQuery = await db.query.mixes.findFirst({
|
|
||||||
// where: (mixes, { eq }) => eq(mixes.id, mixId),
|
|
||||||
// });
|
|
||||||
|
|
||||||
// if (!mixQuery) {
|
|
||||||
// throw new Error("Unable to find mix");
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// return `https://argle.bargle/${mixQuery.audioUrl}`;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default mixService;
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
import { type AdapterUser, type Adapter } from "next-auth/adapters";
|
|
||||||
|
|
||||||
export default function AspNetIdentityAdapter(baseUrl: string): Adapter {
|
|
||||||
return {
|
|
||||||
async createUser(user: Omit<AdapterUser, "id">): Promise<AdapterUser> {
|
|
||||||
const url = `${baseUrl}/account/register`;
|
|
||||||
const result = await fetch(url, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
userName: user.username,
|
|
||||||
password: "password",
|
|
||||||
confirmPassword: "password",
|
|
||||||
displayName: user.name,
|
|
||||||
email: user.email,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.status === 200) {
|
|
||||||
return result.json();
|
|
||||||
} else if (result.status === 400) {
|
|
||||||
console.log("authService", "registerUser", result);
|
|
||||||
}
|
|
||||||
throw Error(`Unable to create user: result ${result.status}`);
|
|
||||||
},
|
|
||||||
async getUser(id) {
|
|
||||||
throw Error(`Not implemented`);
|
|
||||||
},
|
|
||||||
async getUserByEmail(email) {
|
|
||||||
throw Error(`Not implemented`);
|
|
||||||
},
|
|
||||||
async getUserByAccount({ providerAccountId, provider }) {
|
|
||||||
throw Error(`Not implemented`);
|
|
||||||
},
|
|
||||||
async updateUser(user) {
|
|
||||||
throw Error(`Not implemented`);
|
|
||||||
},
|
|
||||||
async deleteUser(userId) {
|
|
||||||
throw Error(`Not implemented`);
|
|
||||||
},
|
|
||||||
async linkAccount(account) {
|
|
||||||
throw Error(`Not implemented`);
|
|
||||||
},
|
|
||||||
async unlinkAccount({ providerAccountId, provider }) {
|
|
||||||
throw Error(`Not implemented`);
|
|
||||||
},
|
|
||||||
async createSession({ sessionToken, userId, expires }) {
|
|
||||||
throw Error(`Not implemented`);
|
|
||||||
},
|
|
||||||
async getSessionAndUser(sessionToken) {
|
|
||||||
throw Error(`Not implemented`);
|
|
||||||
},
|
|
||||||
async updateSession({ sessionToken }) {
|
|
||||||
throw Error(`Not implemented`);
|
|
||||||
},
|
|
||||||
async deleteSession(sessionToken) {
|
|
||||||
throw Error(`Not implemented`);
|
|
||||||
},
|
|
||||||
async createVerificationToken({ identifier, expires, token }) {
|
|
||||||
throw Error(`Not implemented`);
|
|
||||||
},
|
|
||||||
async useVerificationToken({ identifier, token }) {
|
|
||||||
throw Error(`Not implemented`);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
110
src/lib/services/auth/config.ts
Normal file
110
src/lib/services/auth/config.ts
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import { type AuthTokenModel } from "@/lib/models";
|
||||||
|
import type { Session, AuthOptions } from "next-auth";
|
||||||
|
import AuthService from "../api/auth-service";
|
||||||
|
import jwt_decode, { type JwtPayload } from "jwt-decode";
|
||||||
|
import CredentialsProvider from "next-auth/providers/credentials";
|
||||||
|
import ProfileService from "../api/profile-service";
|
||||||
|
import { type JWT } from "next-auth/jwt";
|
||||||
|
import logger from "@/lib/logger";
|
||||||
|
|
||||||
|
export const authOptions: AuthOptions = {
|
||||||
|
secret: process.env.NEXTAUTH_SECRET,
|
||||||
|
debug: process.env.NODE_ENV === "development",
|
||||||
|
session: {
|
||||||
|
maxAge: 30 * 24 * 60 * 60, //30 days
|
||||||
|
updateAge: 24 * 60 * 60, // 24 hours
|
||||||
|
},
|
||||||
|
pages: {
|
||||||
|
signIn: "/auth/login",
|
||||||
|
newUser: "/auth/register",
|
||||||
|
signOut: "/",
|
||||||
|
},
|
||||||
|
providers: [
|
||||||
|
CredentialsProvider({
|
||||||
|
name: "Email and Password",
|
||||||
|
credentials: {
|
||||||
|
username: {
|
||||||
|
label: "Username",
|
||||||
|
type: "text",
|
||||||
|
placeholder: "Username or email address",
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
label: "Password",
|
||||||
|
type: "password",
|
||||||
|
placeholder: "Password",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
authorize: async (credentials, _req): Promise<any> => {
|
||||||
|
logger.info({ authorize: "Authorizing" });
|
||||||
|
try {
|
||||||
|
if (!credentials) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const token = await new AuthService().getAuthToken(
|
||||||
|
credentials.username,
|
||||||
|
credentials.password,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const decodedToken = jwt_decode<JwtPayload & AuthTokenModel>(
|
||||||
|
token.access_token,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (decodedToken && decodedToken.sub) {
|
||||||
|
// const session: Session = {
|
||||||
|
// id: decodedToken.sub as string,
|
||||||
|
// accessToken: token.access_token,
|
||||||
|
// expires: `${token.expires_in}`,
|
||||||
|
// user: {
|
||||||
|
// id: decodedToken.id,
|
||||||
|
// email: decodedToken.email,
|
||||||
|
// username: decodedToken.name,
|
||||||
|
// name: decodedToken.displayName,
|
||||||
|
// slug: decodedToken.slug,
|
||||||
|
// profileImage: decodedToken.profileImage,
|
||||||
|
// accessToken: token.access_token,
|
||||||
|
// },
|
||||||
|
// };
|
||||||
|
const r = {
|
||||||
|
id: decodedToken.id,
|
||||||
|
email: decodedToken.email,
|
||||||
|
username: decodedToken.name,
|
||||||
|
name: decodedToken.displayName,
|
||||||
|
slug: decodedToken.slug,
|
||||||
|
profileImage: decodedToken.profileImage,
|
||||||
|
accessToken: token.access_token,
|
||||||
|
};
|
||||||
|
logger.debug("config", "authorize_returns", r);
|
||||||
|
return Promise.resolve(r);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(`Error authorizing: ${err}`);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
callbacks: {
|
||||||
|
async session({ session, token }: { session: Session; token: JWT }) {
|
||||||
|
const profile = await new ProfileService(
|
||||||
|
session.accessToken,
|
||||||
|
).getProfile();
|
||||||
|
if (!profile) {
|
||||||
|
return Promise.resolve(session);
|
||||||
|
}
|
||||||
|
session.user.profile = profile;
|
||||||
|
logger.debug("config", "callback_session_Returns", session);
|
||||||
|
return session;
|
||||||
|
},
|
||||||
|
jwt: async ({ user, token }): Promise<JWT> => {
|
||||||
|
if (user) {
|
||||||
|
token.user = user;
|
||||||
|
}
|
||||||
|
logger.debug("config", "callback_jwt_Returns", token);
|
||||||
|
return Promise.resolve(token);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
39
src/lib/services/auth/cookie.ts
Normal file
39
src/lib/services/auth/cookie.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { type IncomingMessage } from "http";
|
||||||
|
|
||||||
|
export const getCookieWithName = (name: string, cookieString: string) => {
|
||||||
|
const cookieArray = cookieString.split(";");
|
||||||
|
const cookie = cookieArray.find((x) => x.includes(name));
|
||||||
|
if (!cookie) return null;
|
||||||
|
const value = cookie.split("=")[1];
|
||||||
|
|
||||||
|
return value ? decodeURIComponent(value) : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCookieFromMiddleware = (name: string, req: any) => {
|
||||||
|
const NextRequestMetaSymbol = Reflect.ownKeys(req).find(
|
||||||
|
(key) => key.toString() === "Symbol(NextRequestMeta)",
|
||||||
|
) as string;
|
||||||
|
if (!NextRequestMetaSymbol) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const NextRequestMeta = req[NextRequestMetaSymbol];
|
||||||
|
const cookieString = NextRequestMeta._nextMiddlewareCookie
|
||||||
|
.toString()
|
||||||
|
.replace("Path=/", "");
|
||||||
|
const cookieArray = cookieString.split(";");
|
||||||
|
const cookie = cookieArray.find((x: string | string[]) => x.includes(name));
|
||||||
|
const value = cookie?.split("=")[1];
|
||||||
|
return decodeURIComponent(value.trim());
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCookieFromRequest = (name: string, req: IncomingMessage) => {
|
||||||
|
const cookieString = req.headers.cookie as string;
|
||||||
|
const cookieArray = cookieString?.split(";");
|
||||||
|
const cookie = cookieArray?.find((x) => x.includes(name));
|
||||||
|
if (!cookie) {
|
||||||
|
console.log("cookie not found in the headers, trying the middleware");
|
||||||
|
return getCookieFromMiddleware(name, req);
|
||||||
|
}
|
||||||
|
const value = cookie?.split("=")[1];
|
||||||
|
return value ? decodeURIComponent(value?.trim()) : undefined;
|
||||||
|
};
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
/**
|
|
||||||
* This is the client-side entrypoint for your tRPC API. It is used to create the `api` object which
|
|
||||||
* contains the Next.js App-wrapper, as well as your type-safe React Query hooks.
|
|
||||||
*
|
|
||||||
* We also create a few inference helpers for input and output types.
|
|
||||||
*/
|
|
||||||
import { httpBatchLink, loggerLink } from "@trpc/client";
|
|
||||||
import { createTRPCNext } from "@trpc/next";
|
|
||||||
import { type inferRouterInputs, type inferRouterOutputs } from "@trpc/server";
|
|
||||||
import superjson from "superjson";
|
|
||||||
|
|
||||||
import { type AppRouter } from "@/server/api/root";
|
|
||||||
|
|
||||||
const getBaseUrl = () => {
|
|
||||||
if (typeof window !== "undefined") return ""; // browser should use relative url
|
|
||||||
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; // SSR should use vercel url
|
|
||||||
return `http://localhost:${process.env.PORT ?? 3000}`; // dev SSR should use localhost
|
|
||||||
};
|
|
||||||
|
|
||||||
/** A set of type-safe react-query hooks for your tRPC API. */
|
|
||||||
export const api = createTRPCNext<AppRouter>({
|
|
||||||
config() {
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* Transformer used for data de-serialization from the server.
|
|
||||||
*
|
|
||||||
* @see https://trpc.io/docs/data-transformers
|
|
||||||
*/
|
|
||||||
transformer: superjson,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Links used to determine request flow from client to server.
|
|
||||||
*
|
|
||||||
* @see https://trpc.io/docs/links
|
|
||||||
*/
|
|
||||||
links: [
|
|
||||||
loggerLink({
|
|
||||||
enabled: (opts) =>
|
|
||||||
process.env.NODE_ENV === "development" ||
|
|
||||||
(opts.direction === "down" && opts.result instanceof Error),
|
|
||||||
}),
|
|
||||||
httpBatchLink({
|
|
||||||
url: `${getBaseUrl()}/api/trpc`,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Whether tRPC should await queries when server rendering pages.
|
|
||||||
*
|
|
||||||
* @see https://trpc.io/docs/nextjs#ssr-boolean-default-false
|
|
||||||
*/
|
|
||||||
ssr: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inference helper for inputs.
|
|
||||||
*
|
|
||||||
* @example type HelloInput = RouterInputs['example']['hello']
|
|
||||||
*/
|
|
||||||
export type RouterInputs = inferRouterInputs<AppRouter>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inference helper for outputs.
|
|
||||||
*
|
|
||||||
* @example type HelloOutput = RouterOutputs['example']['hello']
|
|
||||||
*/
|
|
||||||
export type RouterOutputs = inferRouterOutputs<AppRouter>;
|
|
||||||
23
src/types/next-auth.d.ts
vendored
Normal file
23
src/types/next-auth.d.ts
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
import NextAuth, { type DefaultSession } from "next-auth";
|
||||||
|
import type { UserModel } from "@/lib/models";
|
||||||
|
|
||||||
|
declare module "next-auth" {
|
||||||
|
/**
|
||||||
|
* Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
|
||||||
|
*/
|
||||||
|
interface Session {
|
||||||
|
id: string;
|
||||||
|
accessToken: string;
|
||||||
|
user: User & DefaultSession["user"];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface User {
|
||||||
|
username: string;
|
||||||
|
slug: string;
|
||||||
|
email: string;
|
||||||
|
name: string | null;
|
||||||
|
profileImage: string | null;
|
||||||
|
profile?: UserModel;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es2017",
|
"target": "es2017",
|
||||||
"lib": [
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
"dom",
|
|
||||||
"dom.iterable",
|
|
||||||
"esnext"
|
|
||||||
],
|
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"checkJs": true,
|
"checkJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
@@ -21,10 +17,9 @@
|
|||||||
"incremental": true,
|
"incremental": true,
|
||||||
"noUncheckedIndexedAccess": true,
|
"noUncheckedIndexedAccess": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
|
"typeRoots": ["src/types"],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": [
|
"@/*": ["./src/*"]
|
||||||
"./src/*"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
@@ -41,7 +36,5 @@
|
|||||||
"**/*.mjs",
|
"**/*.mjs",
|
||||||
".next/types/**/*.ts"
|
".next/types/**/*.ts"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": ["node_modules"]
|
||||||
"node_modules"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
20
types/next-auth.d.ts
vendored
20
types/next-auth.d.ts
vendored
@@ -1,20 +0,0 @@
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
import NextAuth from "next-auth";
|
|
||||||
|
|
||||||
declare module "next-auth" {
|
|
||||||
interface Session {
|
|
||||||
id: string;
|
|
||||||
user: User;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface User {
|
|
||||||
id: string;
|
|
||||||
username: string;
|
|
||||||
email: string;
|
|
||||||
name: string | null;
|
|
||||||
bio: string | null;
|
|
||||||
profileImage: string | null;
|
|
||||||
headerImage: string | null;
|
|
||||||
urls: string[] | undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user