Skip to content
Merged
Show file tree
Hide file tree
Changes from 168 commits
Commits
Show all changes
170 commits
Select commit Hold shift + click to select a range
d6ea00f
Get basic working (#109)
itexpert120 Mar 25, 2025
810bb9a
fix polyfills
elliotBraem Mar 25, 2025
e8599af
Explore Page (#108)
saadiqbal-dev Mar 25, 2025
30cbbfe
Header + Explore Page Style (#113)
saadiqbal-dev Mar 27, 2025
d4018f7
Profile page (#120)
itexpert120 Apr 11, 2025
cf66339
[FEATURE] Create Feed Page - DRAFT (#121)
saadiqbal-dev Apr 11, 2025
79d572d
Curate Engine Step 1
elliotBraem Apr 11, 2025
db92ef9
update to main
elliotBraem Apr 11, 2025
e369f24
fmt
elliotBraem Apr 11, 2025
1b4c006
Feat/submissions page (#127)
saadiqbal-dev Apr 28, 2025
8d08308
Merge branch 'main' of https://github.com/PotLock/curatedotfun into s…
elliotBraem Apr 28, 2025
908f537
Feed Page Tabs (#130)
saadiqbal-dev Apr 30, 2025
6d9aeb9
Merge branch 'main' of https://github.com/PotLock/curatedotfun into s…
elliotBraem May 1, 2025
6fc7637
set tanstack routes (#132)
elliotBraem May 1, 2025
ea2137c
[Task]: Add connect button to feed page (#131)
louisdevzz May 1, 2025
0ecfab3
uses prod data
elliotBraem May 2, 2025
cd12908
Update changes to latest staging
saadiqbal-dev May 2, 2025
ba55dd3
Revert "Update changes to latest staging"
saadiqbal-dev May 2, 2025
1c4044a
Fix Sort By Oldest (#136)
louisdevzz May 9, 2025
8cd6e82
UI fixes (#138)
saadiqbal-dev May 9, 2025
5e91466
Fix: Leaderboard improvements (#140)
louisdevzz May 9, 2025
59de163
clean up
elliotBraem May 10, 2025
4a411fb
wallet wip
elliotBraem May 12, 2025
72ea8fa
todo
elliotBraem May 12, 2025
716b956
Merge branch 'main' of https://github.com/PotLock/curatedotfun into s…
elliotBraem May 12, 2025
94bf4e4
Merge branch 'staging' into feat/wallet
elliotBraem May 12, 2025
3ad71c0
auth flow, wip
elliotBraem May 12, 2025
5561575
types clean up
elliotBraem May 12, 2025
8dda6af
fix types
elliotBraem May 12, 2025
9078b4e
login modal wip
elliotBraem May 12, 2025
7110ff5
modals
elliotBraem May 12, 2025
af6bfda
controller, service, successful create account
elliotBraem May 13, 2025
c3b2582
clean with data, metadata, and pattern, validation, and json schema
elliotBraem May 13, 2025
592fa49
add migration doc
elliotBraem May 13, 2025
2baaa98
add activity and delete user
elliotBraem May 13, 2025
8b92f11
fix migration
elliotBraem May 13, 2025
ad177fa
add seed remote method
elliotBraem May 13, 2025
620a554
fix naming
elliotBraem May 13, 2025
ac88430
fix script call
elliotBraem May 13, 2025
5edba9d
file extension
elliotBraem May 13, 2025
5cb5ec5
remove build schema
elliotBraem May 13, 2025
6eef52a
proper build time
elliotBraem May 13, 2025
017e0f0
fix Dockerfile
elliotBraem May 13, 2025
1f77f1d
rsbuild
elliotBraem May 13, 2025
5aefc9f
Standard Header Component + Responsivenss Fixes (#146)
saadiqbal-dev May 13, 2025
750a427
fix broken link
saadiqbal-dev May 13, 2025
1473e34
don't distribute on staging
elliotBraem May 14, 2025
6aff38c
fix path
elliotBraem May 14, 2025
d93df7a
env log
elliotBraem May 14, 2025
9bbcc39
comment out
elliotBraem May 14, 2025
754209f
railway env
elliotBraem May 14, 2025
6607554
fix: Profile adjustments (#153)
louisdevzz May 16, 2025
4126f79
Login Modal Fixes (#154)
saadiqbal-dev May 18, 2025
3b1c83b
organize
elliotBraem May 18, 2025
19c491a
fmt
elliotBraem May 18, 2025
14baf96
update feeds (#156)
elliotBraem May 19, 2025
cdf9a14
Leaderboard width fixes
saadiqbal-dev May 19, 2025
6c226e0
feat: save profile image to pinata (#158)
dungpt99 May 20, 2025
b77d92d
Feat Integrate NEAR Solana, Ethereum wallet selection (#159)
louisdevzz May 22, 2025
35bb5eb
Feed Submission + Feed Review Page (#160)
saadiqbal-dev May 22, 2025
1f54369
minor fixes (#164)
saadiqbal-dev May 26, 2025
673b884
remove node-compile-cache
elliotBraem May 29, 2025
8dffc5b
reuse user menu
elliotBraem May 29, 2025
90a4907
header clean up
elliotBraem May 29, 2025
953a1a1
remove how it works
elliotBraem May 29, 2025
a91789e
clean up
elliotBraem May 29, 2025
a6a603e
set submissions at root route
elliotBraem May 29, 2025
5661f48
fmt
elliotBraem May 29, 2025
bd386b0
clean
elliotBraem May 30, 2025
bea8fc5
create is coming soon
elliotBraem May 30, 2025
05de617
clean up
elliotBraem May 30, 2025
b6eea32
user link
elliotBraem May 30, 2025
d1a5f05
Adds caddyfile and frontend clean up (#165)
elliotBraem May 31, 2025
c11ad4e
pnpm lock
elliotBraem May 31, 2025
5dc24a7
fix turbo
elliotBraem May 31, 2025
d308427
fix build
elliotBraem May 31, 2025
6cec47b
db migration
elliotBraem May 31, 2025
8bc9534
without time zone
elliotBraem May 31, 2025
440f0b9
cleans up submission list
elliotBraem May 31, 2025
3631332
Adds shared-db, types package, initial migration (#166)
elliotBraem May 31, 2025
ea685af
update dockerfile
elliotBraem May 31, 2025
a84130e
monorepo
elliotBraem May 31, 2025
3f172eb
working build
elliotBraem May 31, 2025
34cabca
migration service
elliotBraem May 31, 2025
4444954
turbo
elliotBraem May 31, 2025
ac49a25
install pnpm
elliotBraem May 31, 2025
81d5b5b
temp proxy
elliotBraem May 31, 2025
c29851e
no include request headers
elliotBraem May 31, 2025
f05cf7c
clean up
elliotBraem May 31, 2025
8a4e270
proper path
elliotBraem May 31, 2025
74272ba
renaming
elliotBraem May 31, 2025
45d4011
fmt
elliotBraem May 31, 2025
fc74fcb
update caddyfile
elliotBraem Jun 2, 2025
4627a5a
different strategy
elliotBraem Jun 2, 2025
bc58f56
use route
elliotBraem Jun 2, 2025
61dfe9d
fix BACKEND to API
elliotBraem Jun 2, 2025
b8d8e81
ignore temp
elliotBraem Jun 2, 2025
a74187c
temp remove
elliotBraem Jun 2, 2025
60e0e7c
back to orig
elliotBraem Jun 2, 2025
738db5e
turn on auto https
elliotBraem Jun 2, 2025
f9c074a
disable
elliotBraem Jun 2, 2025
7099891
route block
elliotBraem Jun 2, 2025
84eab5c
clean up
elliotBraem Jun 2, 2025
a63b225
configure host
elliotBraem Jun 2, 2025
20c1f28
favicon
elliotBraem Jun 2, 2025
60ef915
add staging domain
elliotBraem Jun 2, 2025
a95fa06
http:
elliotBraem Jun 2, 2025
51440bc
set domain adn host
elliotBraem Jun 2, 2025
82a829d
correct bash
elliotBraem Jun 2, 2025
c03a909
matching host
elliotBraem Jun 2, 2025
45eadff
Adds edit feed and image upload (#168)
elliotBraem Jun 3, 2025
98f8d58
CSR
elliotBraem Jun 3, 2025
fdc1e5d
vercel json
elliotBraem Jun 3, 2025
9c74b63
move
elliotBraem Jun 3, 2025
d99c49b
temp disable auth
elliotBraem Jun 3, 2025
7e8c95e
set image
elliotBraem Jun 3, 2025
97d9c95
fix query
elliotBraem Jun 3, 2025
18a9943
submisison service running
elliotBraem Jun 4, 2025
79b31f9
Migrates submission service, is running (#169)
elliotBraem Jun 4, 2025
b704abd
fix config path
elliotBraem Jun 4, 2025
9eef768
adds plugins route and integrates with plugin service
elliotBraem Jun 4, 2025
c445a81
remote curate.config.json
elliotBraem Jun 4, 2025
50f935f
plugins table
elliotBraem Jun 4, 2025
ef316a8
adds plugin pages
elliotBraem Jun 5, 2025
6edaccb
set type
elliotBraem Jun 5, 2025
60c49e6
fix feed types
elliotBraem Jun 5, 2025
1013e80
plguin errors
elliotBraem Jun 5, 2025
24764f7
env injection
elliotBraem Jun 7, 2025
6003d2e
fix queries
elliotBraem Jun 9, 2025
c2ab5f4
fix migration
elliotBraem Jun 9, 2025
c2bc5b5
fix migration
elliotBraem Jun 9, 2025
05dea50
fix migration
elliotBraem Jun 9, 2025
a10bb98
redo migration
elliotBraem Jun 9, 2025
45a5d2f
decouples moderation
elliotBraem Jun 9, 2025
3475cf5
fix status
elliotBraem Jun 10, 2025
dc6ea46
fix feeds
elliotBraem Jun 10, 2025
8c60522
hide moderation actions
elliotBraem Jun 10, 2025
c53a8d7
Merge branch 'main' of https://github.com/PotLock/curatedotfun into s…
elliotBraem Jun 10, 2025
a328e69
adds overwrite script
elliotBraem Jun 10, 2025
8bc0213
migrate timestamps
elliotBraem Jun 10, 2025
f05166f
Merge branch 'feat/overwrite-script' into staging
elliotBraem Jun 10, 2025
11c43dc
better date handling
elliotBraem Jun 10, 2025
a0cbdf0
fix
elliotBraem Jun 10, 2025
79f280d
Merge branch 'main' into staging
elliotBraem Jun 10, 2025
8d11e53
Adds auth (#174)
elliotBraem Jun 12, 2025
626305e
moderation actions
elliotBraem Jun 12, 2025
8683ad6
hide moderation actions
elliotBraem Jun 12, 2025
1589483
hack
elliotBraem Jun 12, 2025
ed311a9
fix flow
elliotBraem Jun 12, 2025
2f28249
enable hosted service
elliotBraem Jun 12, 2025
59f5453
adds user identities and connect platform
elliotBraem Jun 17, 2025
21610b3
create profile
elliotBraem Jun 17, 2025
8ecbaec
ensureUserExists
elliotBraem Jun 17, 2025
d058b6a
set network for staging
elliotBraem Jun 17, 2025
2d8de34
near account id
elliotBraem Jun 17, 2025
c76d376
auth provider id not null
elliotBraem Jun 19, 2025
3e4ba02
init near
elliotBraem Jun 19, 2025
439788a
fix monorepo build
elliotBraem Jun 19, 2025
5269bea
fmt
elliotBraem Jun 19, 2025
6f67f64
user clean up
elliotBraem Jun 20, 2025
0e273d3
update moderation
elliotBraem Jun 20, 2025
0d17194
switch to crosspost connected
elliotBraem Jun 20, 2025
a95a282
fix user id and error
elliotBraem Jun 20, 2025
0c9f607
server side
elliotBraem Jun 20, 2025
2a9db51
moderation hooks
elliotBraem Jun 20, 2025
e2b8161
cleaner logs
elliotBraem Jun 20, 2025
e5e7227
Update apps/api/src/services/moderation.service.ts
elliotBraem Jun 20, 2025
a23e5fd
Update apps/app/src/lib/near.ts
elliotBraem Jun 20, 2025
136ac33
Update apps/app/src/lib/near.ts
elliotBraem Jun 20, 2025
c9acc8d
adds feature flags and moderation service clean up
elliotBraem Jun 20, 2025
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
5 changes: 4 additions & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@
"nock": "^13.5.4",
"typescript": "^5.3.3",
"wait-on": "^8.0.2",
"zod": "^3.24.4"
"zod": "^3.25.62"
},
"dependencies": {
"@crosspost/sdk": "^0.3.0",
"@crosspost/types": "^0.3.0",
"@crosspost/scheduler-sdk": "^0.1.1",
"@curatedotfun/shared-db": "workspace:*",
"@curatedotfun/types": "workspace:*",
Expand All @@ -59,6 +61,7 @@
"lodash": "^4.17.21",
"mustache": "^4.2.0",
"near-api-js": "^5.1.1",
"near-sign-verify": "^0.3.6",
"ora": "^8.1.1",
"pg": "^8.15.6",
Comment on lines 62 to 66
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Pin cryptography-related dependency for supply-chain safety
"near-sign-verify": "^0.3.6" pulls the latest minor/patch at install-time. Given it is a security-sensitive lib, lock to an exact version (e.g. 0.3.6) and add an automated update workflow instead of using ^.

-    "near-sign-verify": "^0.3.6",
+    "near-sign-verify": "0.3.6",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"mustache": "^4.2.0",
"near-api-js": "^5.1.1",
"near-sign-verify": "^0.3.6",
"ora": "^8.1.1",
"pg": "^8.15.6",
"mustache": "^4.2.0",
"near-api-js": "^5.1.1",
- "near-sign-verify": "^0.3.6",
+ "near-sign-verify": "0.3.6",
"ora": "^8.1.1",
"pg": "^8.15.6",
🤖 Prompt for AI Agents
In apps/api/package.json around lines 60 to 64, the dependency
"near-sign-verify" is specified with a caret (^) which allows minor and patch
updates. Since this is a security-sensitive library, change the version
specifier from "^0.3.6" to the exact version "0.3.6" to prevent automatic
updates. Additionally, set up an automated workflow to handle updates safely
instead of relying on caret versioning.

"pinata-web3": "^0.5.4",
Expand Down
9 changes: 5 additions & 4 deletions apps/api/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import { secureHeaders } from "hono/secure-headers";
import { db } from "./db";
import { apiRoutes } from "./routes/api";
import { AppInstance, Env } from "./types/app";
import { web3AuthJwtMiddleware } from "./utils/auth";
import { getAllowedOrigins } from "./utils/config";
import { errorHandler } from "./utils/error";
import { ServiceProvider } from "./utils/service-provider";
import { logger } from "utils/logger";
import { createAuthMiddleware } from "./middlewares/auth.middleware";

const ALLOWED_ORIGINS = getAllowedOrigins();

Expand All @@ -18,7 +19,7 @@ export async function createApp(): Promise<AppInstance> {
const app = new Hono<Env>();

app.onError((err, c) => {
return errorHandler(err, c);
return errorHandler(err, c, logger);
});

app.use(
Expand All @@ -44,8 +45,8 @@ export async function createApp(): Promise<AppInstance> {
await next();
});

// Authentication middleware
app.use("*", web3AuthJwtMiddleware);
// Apply auth middleware to all /api routes
app.use("/api/*", createAuthMiddleware());

Comment on lines +48 to 50
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

DELETE/PATCH verbs missing from CORS allow-list

Now that createAuthMiddleware is scoped to /api/*, destructive operations (e.g. DELETE /api/plugins/:id) will originate from the browser and fail pre-flight because CORS allowMethods only includes "GET", "POST" (lines 25-38).

-      allowMethods: ["GET", "POST"],
+      allowMethods: ["GET", "POST", "PUT", "PATCH", "DELETE"],

Without this change the new plugin delete flow from the frontend will hit a CORS policy: Method DELETE is not allowed.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/api/src/app.ts around lines 25 to 38, the CORS middleware's allowMethods
list currently only includes "GET" and "POST", which causes pre-flight requests
for DELETE and PATCH methods to fail. Update the CORS configuration to include
"DELETE" and "PATCH" in the allowMethods array to enable these HTTP verbs for
/api routes and prevent CORS policy errors during destructive operations.

// Mount API routes
app.route("/api", apiRoutes);
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ import { Pool } from "pg";
const pool = new Pool({
connectionString: process.env.DATABASE_URL!,
});
const db: DB = drizzle(pool, { schema });
const db: DB = drizzle({ client: pool, schema, casing: "snake_case" });

export { db, pool };
54 changes: 54 additions & 0 deletions apps/api/src/middlewares/auth.middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Context, MiddlewareHandler, Next } from "hono";
import { verify } from "near-sign-verify";

export function createAuthMiddleware(): MiddlewareHandler {
return async (c: Context, next: Next) => {
const method = c.req.method;
let accountId: string | null = null;

if (method === "GET") {
const nearAccountHeader = c.req.header("X-Near-Account");
if (
nearAccountHeader &&
nearAccountHeader.toLowerCase() !== "anonymous"
) {
accountId = nearAccountHeader;
}
// If header is missing or "anonymous", accountId remains null
c.set("accountId", accountId);
await next();
Comment on lines +9 to +19
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

GET requests can impersonate any account via an un-verified header

Accepting X-Near-Account at face value allows clients to forge someone else’s accountId on every read-only endpoint. Even if current routes are “public”, future changes may rely on accountId for personalised or privileged data, turning this into an escalation vector.

Require a signed token for any request that needs identity, or introduce an explicit allow-list (e.g. only null for anonymous) and fetch identity server-side if you need it for caching.

🤖 Prompt for AI Agents
In apps/api/src/middlewares/auth.middleware.ts around lines 9 to 19, the code
currently trusts the X-Near-Account header for GET requests, allowing clients to
impersonate any account. To fix this, remove the direct use of this header for
setting accountId. Instead, require a signed token to verify identity for any
request needing an accountId, or implement an explicit allow-list that only
permits null or anonymous values from the header and fetch the verified identity
server-side before setting accountId.

return;
}

// For non-GET requests (POST, PUT, DELETE, PATCH, etc.)
const authHeader = c.req.header("Authorization");
if (!authHeader || !authHeader.startsWith("Bearer ")) {
c.status(401);
return c.json({
error: "Unauthorized",
details: "Missing or malformed Authorization header.",
});
}

const token = authHeader.substring(7); // Remove "Bearer "

try {
const verificationResult = await verify(token, {
expectedRecipient: "curatefun.near",
requireFullAccessKey: false,
nonceMaxAge: 300000, // 5 mins
});

accountId = verificationResult.accountId;
c.set("accountId", accountId);
await next();
} catch (error) {
console.error("Token verification error:", error);
c.status(401);
return c.json({
error: "Unauthorized",
details: "Invalid token signature or recipient.",
});
}
};
}
137 changes: 131 additions & 6 deletions apps/api/src/routes/api/feeds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { Env } from "../../types/app";
import { badRequest } from "../../utils/error";
import { logger } from "../../utils/logger";
import { insertFeedSchema, updateFeedSchema } from "@curatedotfun/shared-db";
import { z } from "zod"; // Added import for z
import { zValidator } from "@hono/zod-validator"; // Added import for zValidator
import { ModerationService } from "../../services/moderation.service"; // Added import for ModerationService

const feedsRoutes = new Hono<Env>();

Expand All @@ -25,17 +28,49 @@ feedsRoutes.get("/", async (c) => {
* Create a new feed
*/
feedsRoutes.post("/", async (c) => {
const accountId = c.get("accountId");
if (!accountId) {
return c.json(
{ error: "Unauthorized. User must be logged in to create a feed." },
401,
);
}

const body = await c.req.json();
const validationResult = insertFeedSchema.safeParse(body);
const partialValidationResult = insertFeedSchema
.omit({ created_by: true })
.safeParse(body);

if (!validationResult.success) {
return badRequest(c, "Invalid feed data", validationResult.error.flatten());
if (!partialValidationResult.success) {
return badRequest(
c,
"Invalid feed data",
partialValidationResult.error.flatten(),
);
}

const feedDataWithCreator = {
...partialValidationResult.data,
created_by: accountId,
};

const finalValidationResult = insertFeedSchema.safeParse(feedDataWithCreator);
if (!finalValidationResult.success) {
logger.error(
"Error in final validation after adding created_by",
finalValidationResult.error,
);
return badRequest(
c,
"Internal validation error",
finalValidationResult.error.flatten(),
);
}
Comment on lines 30 to 68
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

JSON parse can throw outside try/catch.

await c.req.json() is executed before validation but outside a guarded block. Malformed JSON will bubble an uncaught exception and yield a 500.
Wrap the parse in the existing validation path:

-const body = await c.req.json();
+let body;
+try {
+  body = await c.req.json();
+} catch {
+  return badRequest(c, "Invalid JSON payload");
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
feedsRoutes.post("/", async (c) => {
const accountId = c.get("accountId");
if (!accountId) {
return c.json(
{ error: "Unauthorized. User must be logged in to create a feed." },
401,
);
}
const body = await c.req.json();
const validationResult = insertFeedSchema.safeParse(body);
const partialValidationResult = insertFeedSchema
.omit({ created_by: true })
.safeParse(body);
if (!validationResult.success) {
return badRequest(c, "Invalid feed data", validationResult.error.flatten());
if (!partialValidationResult.success) {
return badRequest(
c,
"Invalid feed data",
partialValidationResult.error.flatten(),
);
}
const feedDataWithCreator = {
...partialValidationResult.data,
created_by: accountId,
};
const finalValidationResult = insertFeedSchema.safeParse(feedDataWithCreator);
if (!finalValidationResult.success) {
logger.error(
"Error in final validation after adding created_by",
finalValidationResult.error,
);
return badRequest(
c,
"Internal validation error",
finalValidationResult.error.flatten(),
);
}
feedsRoutes.post("/", async (c) => {
const accountId = c.get("accountId");
if (!accountId) {
return c.json(
{ error: "Unauthorized. User must be logged in to create a feed." },
401,
);
}
let body;
try {
body = await c.req.json();
} catch {
return badRequest(c, "Invalid JSON payload");
}
const partialValidationResult = insertFeedSchema
.omit({ created_by: true })
.safeParse(body);
if (!partialValidationResult.success) {
return badRequest(
c,
"Invalid feed data",
partialValidationResult.error.flatten(),
);
}
const feedDataWithCreator = {
...partialValidationResult.data,
created_by: accountId,
};
const finalValidationResult = insertFeedSchema.safeParse(feedDataWithCreator);
if (!finalValidationResult.success) {
logger.error(
"Error in final validation after adding created_by",
finalValidationResult.error,
);
return badRequest(
c,
"Internal validation error",
finalValidationResult.error.flatten(),
);
}
// ...
});
🤖 Prompt for AI Agents
In apps/api/src/routes/api/feeds.ts between lines 30 and 68, the call to await
c.req.json() is outside any try/catch block, so malformed JSON will cause an
uncaught exception and a 500 error. To fix this, wrap the JSON parsing in a
try/catch block that catches parsing errors and returns a proper 400 bad request
response with an error message indicating invalid JSON input.


const sp = c.get("sp");
const feedService = sp.getFeedService();
try {
const newFeed = await feedService.createFeed(validationResult.data);
const newFeed = await feedService.createFeed(finalValidationResult.data);
return c.json(newFeed, 201);
} catch (error) {
logger.error("Error creating feed:", error);
Expand Down Expand Up @@ -66,16 +101,37 @@ feedsRoutes.get("/:feedId", async (c) => {
* Update an existing feed
*/
feedsRoutes.put("/:feedId", async (c) => {
const accountId = c.get("accountId");
if (!accountId) {
return c.json(
{ error: "Unauthorized. User must be logged in to update a feed." },
401,
);
}

const feedId = c.req.param("feedId");
const sp = c.get("sp");
const feedService = sp.getFeedService();

const canUpdate = await feedService.hasPermission(
accountId,
feedId,
"update",
);
if (!canUpdate) {
return c.json(
{ error: "Forbidden. You do not have permission to update this feed." },
403,
);
}

const body = await c.req.json();
const validationResult = updateFeedSchema.safeParse(body);

if (!validationResult.success) {
return badRequest(c, "Invalid feed data", validationResult.error.flatten());
}

const sp = c.get("sp");
const feedService = sp.getFeedService();
try {
const updatedFeed = await feedService.updateFeed(
feedId,
Expand All @@ -97,6 +153,14 @@ feedsRoutes.put("/:feedId", async (c) => {
* Example: /api/feeds/solana/process?distributors=@curatedotfun/rss
*/
feedsRoutes.post("/:feedId/process", async (c) => {
const accountId = c.get("accountId");
if (!accountId) {
return c.json(
{ error: "Unauthorized. User must be logged in to process a feed." },
401,
);
}

const sp = c.get("sp");
const feedService = sp.getFeedService();

Expand Down Expand Up @@ -127,9 +191,30 @@ feedsRoutes.post("/:feedId/process", async (c) => {
* Delete a specific feed by its ID
*/
feedsRoutes.delete("/:feedId", async (c) => {
const accountId = c.get("accountId");
if (!accountId) {
return c.json(
{ error: "Unauthorized. User must be logged in to delete a feed." },
401,
);
}

const feedId = c.req.param("feedId");
const sp = c.get("sp");
const feedService = sp.getFeedService();

const canDelete = await feedService.hasPermission(
accountId,
feedId,
"delete",
);
if (!canDelete) {
return c.json(
{ error: "Forbidden. You do not have permission to delete this feed." },
403,
);
}

try {
const result = await feedService.deleteFeed(feedId);
if (!result) {
Expand All @@ -142,4 +227,44 @@ feedsRoutes.delete("/:feedId", async (c) => {
}
});

const feedParamSchemaCanModerate = z.object({
// Renamed to avoid conflict if other schemas exist
feedId: z.string().min(1, "Feed ID is required"),
});

feedsRoutes.get(
"/:feedId/can-moderate",
zValidator("param", feedParamSchemaCanModerate),
async (c) => {
const { feedId } = c.req.valid("param");
const sp = c.get("sp");
const moderationService =
sp.getService<ModerationService>("moderationService");

const actingAccountId = c.get("accountId");

if (!actingAccountId) {
return c.json({ canModerate: false, reason: "User not authenticated" });
}

try {
const canModerate =
await moderationService.checkUserFeedModerationPermission(
feedId,
actingAccountId,
);
return c.json({ canModerate });
} catch (error: any) {
logger.error(
`Error in /:feedId/can-moderate for feed ${feedId}, user ${actingAccountId}:`,
error,
);
return c.json(
{ canModerate: false, error: "Failed to check moderation permission" },
500,
);
}
},
);

export { feedsRoutes };
Loading
Loading