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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/(config-wrapper)/settings/account/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert"
import { ChangePasswordError, ChangePasswordRequest, ChangePasswordSuccess } from "@/app/api/v1/auth/change-password/route"
import { ChangePasswordRequest } from "@/packages/sdk/types/auth/change-password"
import { useRouter } from "next/navigation"
import { postAuthChangePassword } from "@/lib/apiClient";
import { DialogDescription } from "@radix-ui/react-dialog"
Expand Down
20 changes: 2 additions & 18 deletions app/api/v1/auth/change-password/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { getServerPB } from "@/lib/pb";
import { ChangePasswordRequest, ChangePasswordResponse } from "@/packages/sdk/types/auth/change-password";
import { NextResponse } from "next/server";

/** Changes the authenticated user's password and reissues a token when re-auth succeeds. */
export async function POST(request: Request): Promise<NextResponse<ChangePasswordResponse>> {
try {
const body = (await request.json().catch(() => ({}))) as ChangePasswordRequest;
Expand Down Expand Up @@ -81,21 +83,3 @@ export async function POST(request: Request): Promise<NextResponse<ChangePasswor
return NextResponse.json({ error: errorMsg }, { status: 500 });
}
}

export interface ChangePasswordRequest {
email?: string;
oldPassword: string;
newPassword: string;
confirmPassword: string;
}

export interface ChangePasswordSuccess {
message: string;
token?: string | null;
}

export interface ChangePasswordError {
error: string;
}

export type ChangePasswordResponse = ChangePasswordSuccess | ChangePasswordError;
186 changes: 94 additions & 92 deletions app/api/v1/news/feed-category-rename/route.ts
Original file line number Diff line number Diff line change
@@ -1,111 +1,113 @@
import { NextRequest, NextResponse } from "next/server";
import { getServerPB, getSuperuserPB } from "@/lib/pb";
import { getServerPB } from "@/lib/pb";
import { getSuperuserPB } from "@/lib/pb";
import { NextRequest } from "next/server";
import { NextResponse } from "next/server";

interface RenameRequestBody {
oldCategory: string;
newCategory: string;
oldCategory: string;
newCategory: string;
}

function escapeFilter(str: string) {
return str.replace(/"/g, '\\"');
}
/** Renames a feed category across all saved subscriptions for the current user. */
export async function POST(req: NextRequest) {
try {
const body = await validateBody(req);

async function validateBody(req: NextRequest): Promise<RenameRequestBody> {
let json: any;
const serverPb = getServerPB();
const authHeader = req.headers.get("authorization");

if (!authHeader?.startsWith("Bearer ")) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

const token = authHeader.split(" ")[1];
serverPb.authStore.save(token, null);

let authData;
try {
json = await req.json();
authData = await serverPb.collection("users").authRefresh();
} catch {
throw new Response("Invalid or empty JSON body", { status: 400 });
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

if (!json || typeof json !== "object") {
throw new Response("Invalid JSON body", { status: 400 });
const userId = authData?.record?.id;
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

if (!json.oldCategory || typeof json.oldCategory !== "string") {
throw new Response("'oldCategory' is required", { status: 400 });
const superPb = await getSuperuserPB();
const filter = `userId="${escapeFilter(userId)}"`;

let record: any = null;
try {
record = await superPb.collection("newsFeeds").getFirstListItem(filter);
} catch (e: any) {
if (e?.status === 404) {
return NextResponse.json({ message: "No feeds found" }, { status: 404 });
}
throw e;
}

if (!json.newCategory || typeof json.newCategory !== "string") {
throw new Response("'newCategory' is required", { status: 400 });
const oldCat = body.oldCategory;
const newCat = body.newCategory;

let current = Array.isArray(record.subscriptions)
? record.subscriptions.map((x: any) => (typeof x === "string" ? { feedUrl: x } : x))
: [];

let changed = false;
current = current.map((s: any) => {
const cat = (s.category ?? "").toString();
if (cat === oldCat) {
changed = true;
return { ...s, category: newCat };
}
return s;
});

if (changed) {
await superPb.collection("newsFeeds").update(record.id, {
subscriptions: current,
});
}

return {
oldCategory: json.oldCategory,
newCategory: json.newCategory,
};
return NextResponse.json({ message: "Category rename applied", changed }, { status: 200 });
} catch (err: any) {
console.error("Error in POST /api/v1/news/feed-category-rename:", err);
return NextResponse.json(
{ error: "Internal Server Error", details: String(err?.message ?? err) },
{ status: 500 }
);
}
}

export async function POST(req: NextRequest) {
try {
const body = await validateBody(req);

const serverPb = getServerPB();
const authHeader = req.headers.get("authorization");

if (!authHeader?.startsWith("Bearer ")) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

const token = authHeader.split(" ")[1];
serverPb.authStore.save(token, null);

let authData;
try {
authData = await serverPb.collection("users").authRefresh();
} catch {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

const userId = authData?.record?.id;
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

const superPb = await getSuperuserPB();

const filter = `userId="${escapeFilter(userId)}"`;

let record: any = null;
try {
record = await superPb.collection("newsFeeds").getFirstListItem(filter);
} catch (e: any) {
if (e?.status === 404) {
return NextResponse.json({ message: "No feeds found" }, { status: 404 });
}
throw e;
}

const oldCat = body.oldCategory;
const newCat = body.newCategory;

let current = Array.isArray(record.subscriptions)
? record.subscriptions.map((x: any) => (typeof x === "string" ? { feedUrl: x } : x))
: [];

let changed = false;
current = current.map((s: any) => {
const cat = (s.category ?? "").toString();
if (cat === oldCat) {
changed = true;
return { ...s, category: newCat };
}
return s;
});

if (changed) {
await superPb.collection("newsFeeds").update(record.id, {
subscriptions: current,
});
}

return NextResponse.json({ message: "Category rename applied", changed }, { status: 200 });
} catch (err: any) {
console.error("Error in POST /api/v1/news/feed-category-rename:", err);
return NextResponse.json(
{ error: "Internal Server Error", details: String(err?.message ?? err) },
{ status: 500 }
);
}
function escapeFilter(str: string) {
return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
}

async function validateBody(req: NextRequest): Promise<RenameRequestBody> {
let json: any;

try {
json = await req.json();
} catch {
throw new Response("Invalid or empty JSON body", { status: 400 });
}

if (!json || typeof json !== "object") {
throw new Response("Invalid JSON body", { status: 400 });
}

if (!json.oldCategory || typeof json.oldCategory !== "string") {
throw new Response("'oldCategory' is required", { status: 400 });
}

if (!json.newCategory || typeof json.newCategory !== "string") {
throw new Response("'newCategory' is required", { status: 400 });
}

return {
oldCategory: json.oldCategory,
newCategory: json.newCategory,
};
}
Loading