Skip to content

Commit ebf386c

Browse files
elliotBraemitexpert120codingshotlouisdevzzsaadiqbal-dev
authored
Enable self service (#175)
* Get basic working (#109) * Get basic working * adds rss feeds * added stablewatch founder and stablecoin intern from messaria as approvers on stablecoins feed * fixes to config for grants, sui, and telegram channels (#102) * add query param for selective processing (#103) * adds query param to process * add query param for processing * simplify * add tags * Feat: implement frontend leaderboard (#93) * feat: implement frontend leaderboard * feat: implement a leaderboard in frontend * feat: implemented leaderboard * fix: rebuild implement leaderboard * fix: prettier * fix: prettier * fix: reimplement frontend leaderboard * fix: implement frontend leaderboard * approval rate * sets approval rate and hides curator --------- Co-authored-by: Elliot Braem <elliot@ejlbraem.com> * remove tailwind-scrollbar * added bob to desci feed * Get basic working * set .env.example * nitpicks --------- Co-authored-by: Elliot Braem <elliot@ejlbraem.com> Co-authored-by: codingshot <45281667+codingshot@users.noreply.github.com> Co-authored-by: Elliot Braem <elliot@everything.dev> Co-authored-by: Louis <112561517+louisdevzz@users.noreply.github.com> * fix polyfills * Explore Page (#108) * Explore Page - commit-1 * Explore Page - commit-2 * explore page - commit-3 * explore page - commit - 4 * explore page - commit - prettier * explore page responsiveness + code Rabbit Comments * code Rabbit Comments resolved * css updates * css changes 2 * header update + mobile responsive * conflicts resolved * Rebase and changes * Fix fmt * Header + Explore Page Style (#113) * Explore Page - commit-1 * Explore Page - commit-2 * explore page - commit-3 * explore page - commit - 4 * explore page - commit - prettier * explore page responsiveness + code Rabbit Comments * code Rabbit Comments resolved * css updates * css changes 2 * header update + mobile responsive * conflicts resolved * Rebase and changes * Fix fmt * Header Updates + Web3Auth getUserInfo + Explore Page changes * fmt * coderabbit comments resolved * Profile page (#120) * Update the FE to have the profile page (header and tabs init) * Move tabs to it's own component * Add stats and top badges to overview * Finish the overview tab * Update overview page and init content page * feat(profile-page): add Content and My Feed tags * feat(profile-page): finish profile page static UI * refactor: fmt * [FEATURE] Create Feed Page - DRAFT (#121) * Curate Engine Step 1 * content-progress-configuration step-1 * Curation Settings Part 2 and 3 * CodeRabbit Comments Resolved + Mobile Responsive * Responsiveness: empty State, JSON check --------- Co-authored-by: Elliot Braem <elliot@everything.dev> * update to main * fmt * Feat/submissions page (#127) * Submissions Page + Feed Page + Mobile Responsiveness * fixes * craete-feed authenticated user condition * fmt * remove sqlite --------- Co-authored-by: Elliot Braem <elliot@ejlbraem.com> * Feed Page Tabs (#130) * set tanstack routes (#132) * [Task]: Add connect button to feed page (#131) * feat: Add connect button to feed page * fix: recommit * added back to stablecoins feed since stablewatch forked their own * fix packages, update pg, and ignore cloudflare sockets * fmt --------- Co-authored-by: ethnclark <ethanclark1310@gmail.com> Co-authored-by: codingshot <45281667+codingshot@users.noreply.github.com> Co-authored-by: Elliot Braem <elliot@ejlbraem.com> * uses prod data * Update changes to latest staging * Revert "Update changes to latest staging" This reverts commit cd12908. * Fix Sort By Oldest (#136) * fix: Sort By Oldest * fix: Sort By Oldest * fix: Fix RecentSubmissions Sort Order Update * fix: All feed should be hidden, remove double title --------- Co-authored-by: vohuunhan1310@gmail.com <ethanclark1310@gmail.com> * UI fixes (#138) * UI fixes * fmt --------- Co-authored-by: Elliot Braem <elliot@everything.dev> * Fix: Leaderboard improvements (#140) * fix: Leaderboard improvements * fix: fmt * reorganize * remove unused * rename * clean up * fmt --------- Co-authored-by: Elliot Braem <elliot@ejlbraem.com> * clean up * wallet wip * todo * auth flow, wip * types clean up * fix types * login modal wip * modals * controller, service, successful create account * clean with data, metadata, and pattern, validation, and json schema * add migration doc * add activity and delete user * fix migration * add seed remote method * fix naming * fix script call * file extension * remove build schema * proper build time * fix Dockerfile * rsbuild * Standard Header Component + Responsivenss Fixes (#146) * Standard Header Component + Responsivenss Fixes * fmt * rename Hero --------- Co-authored-by: Elliot Braem <elliot@ejlbraem.com> * fix broken link * don't distribute on staging * fix path * env log * comment out * railway env * fix: Profile adjustments (#153) * fix: Leaderboard improvements * fix: fmt * fix: Profile adjustments * fix: resolve conversation * Login Modal Fixes (#154) * Login Modal Fixes * Resolve Comments * container * container fixes --------- Co-authored-by: Elliot Braem <elliot@everything.dev> * organize * fmt * update feeds (#156) * Leaderboard width fixes * feat: save profile image to pinata (#158) * feat(fe): save profile image to pinata * fix: fix comment * Feat Integrate NEAR Solana, Ethereum wallet selection (#159) * fix: Leaderboard improvements * fix: fmt * fix: Profile adjustments * fix: resolve conversation * feat: Integrate NEAR wallet selection * fix: run fmt * fix: add function create accesstokenpayload use wallet selector near * fix: resolve conversation * Feed Submission + Feed Review Page (#160) * Feed Review Page and Feed Creation * fmt * coderabbit comments resolved * comments resolved * comments resolved * reset routeTree * minor fixes (#164) * minor fixes * fmt * remove node-compile-cache * reuse user menu * header clean up * remove how it works * clean up * set submissions at root route * fmt * clean * create is coming soon * clean up * user link * Adds caddyfile and frontend clean up (#165) * removes serve static from backend * fmt * fix build * adds caddyfile * clean up submission feed * pnpm lock * fix turbo * fix build * db migration * without time zone * cleans up submission list * Adds shared-db, types package, initial migration (#166) * init * upgrade tsconfigs * shared-db build * shared-db wip * transfer getAllSubmissions * hooked up * moves to shared-db * fmt * update dockerfile * monorepo * working build * migration service * turbo * install pnpm * temp proxy * no include request headers * clean up * proper path * renaming * fmt * update caddyfile * different strategy * use route * fix BACKEND to API * ignore temp * temp remove * back to orig * turn on auto https * disable * route block * clean up * configure host * favicon * add staging domain * http: * set domain adn host * correct bash * matching host * Adds edit feed and image upload (#168) * adds page * image upload and edit feed * update pnpm lock * CSR * vercel json * move * temp disable auth * set image * fix query * submisison service running * Migrates submission service, is running (#169) * init * wip * clean up * feed list clean up * break up functions * fix config path * adds plugins route and integrates with plugin service * remote curate.config.json * plugins table * adds plugin pages * set type * fix feed types * plguin errors * env injection * fix queries * fix migration * fix migration * fix migration * redo migration * decouples moderation * fix status * fix feeds * hide moderation actions * adds overwrite script * migrate timestamps * better date handling * fix * Adds auth (#174) * adds fastintear auth, init * auth flow * fmt * adds fastintear auth, init * auth flow * fmt * frontend auth * auth middleware * feed protection * fmt * moderation wip * update lock * migration * moderation actions * hide moderation actions * hack * fix flow * enable hosted service * adds user identities and connect platform * create profile * ensureUserExists * set network for staging * near account id * auth provider id not null * init near * fix monorepo build * fmt * user clean up * update moderation * switch to crosspost connected * fix user id and error * server side * moderation hooks * cleaner logs * Update apps/api/src/services/moderation.service.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update apps/app/src/lib/near.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update apps/app/src/lib/near.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * adds feature flags and moderation service clean up --------- Co-authored-by: Zeeshan Ahmad <itexpert120@outlook.com> Co-authored-by: codingshot <45281667+codingshot@users.noreply.github.com> Co-authored-by: Louis <112561517+louisdevzz@users.noreply.github.com> Co-authored-by: Muhammad Saad Iqbal <saadiqbal.dev@outlook.com> Co-authored-by: ethnclark <ethanclark1310@gmail.com> Co-authored-by: dungpt82 <69756171+dungpt99@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent 268a1dc commit ebf386c

File tree

132 files changed

+13127
-9665
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

132 files changed

+13127
-9665
lines changed

apps/api/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@
3333
"nock": "^13.5.4",
3434
"typescript": "^5.3.3",
3535
"wait-on": "^8.0.2",
36-
"zod": "^3.24.4"
36+
"zod": "^3.25.62"
3737
},
3838
"dependencies": {
39+
"@crosspost/sdk": "^0.3.0",
40+
"@crosspost/types": "^0.3.0",
3941
"@crosspost/scheduler-sdk": "^0.1.1",
4042
"@curatedotfun/shared-db": "workspace:*",
4143
"@curatedotfun/types": "workspace:*",
@@ -59,6 +61,7 @@
5961
"lodash": "^4.17.21",
6062
"mustache": "^4.2.0",
6163
"near-api-js": "^5.1.1",
64+
"near-sign-verify": "^0.3.6",
6265
"ora": "^8.1.1",
6366
"pg": "^8.15.6",
6467
"pinata-web3": "^0.5.4",

apps/api/src/app.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import { secureHeaders } from "hono/secure-headers";
44
import { db } from "./db";
55
import { apiRoutes } from "./routes/api";
66
import { AppInstance, Env } from "./types/app";
7-
import { web3AuthJwtMiddleware } from "./utils/auth";
87
import { getAllowedOrigins } from "./utils/config";
98
import { errorHandler } from "./utils/error";
109
import { ServiceProvider } from "./utils/service-provider";
10+
import { logger } from "utils/logger";
11+
import { createAuthMiddleware } from "./middlewares/auth.middleware";
1112

1213
const ALLOWED_ORIGINS = getAllowedOrigins();
1314

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

2021
app.onError((err, c) => {
21-
return errorHandler(err, c);
22+
return errorHandler(err, c, logger);
2223
});
2324

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

47-
// Authentication middleware
48-
app.use("*", web3AuthJwtMiddleware);
48+
// Apply auth middleware to all /api routes
49+
app.use("/api/*", createAuthMiddleware());
4950

5051
// Mount API routes
5152
app.route("/api", apiRoutes);

apps/api/src/db/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ import { Pool } from "pg";
55
const pool = new Pool({
66
connectionString: process.env.DATABASE_URL!,
77
});
8-
const db: DB = drizzle(pool, { schema });
8+
const db: DB = drizzle({ client: pool, schema, casing: "snake_case" });
99

1010
export { db, pool };
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { Context, MiddlewareHandler, Next } from "hono";
2+
import { verify } from "near-sign-verify";
3+
4+
export function createAuthMiddleware(): MiddlewareHandler {
5+
return async (c: Context, next: Next) => {
6+
const method = c.req.method;
7+
let accountId: string | null = null;
8+
9+
if (method === "GET") {
10+
const nearAccountHeader = c.req.header("X-Near-Account");
11+
if (
12+
nearAccountHeader &&
13+
nearAccountHeader.toLowerCase() !== "anonymous"
14+
) {
15+
accountId = nearAccountHeader;
16+
}
17+
// If header is missing or "anonymous", accountId remains null
18+
c.set("accountId", accountId);
19+
await next();
20+
return;
21+
}
22+
23+
// For non-GET requests (POST, PUT, DELETE, PATCH, etc.)
24+
const authHeader = c.req.header("Authorization");
25+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
26+
c.status(401);
27+
return c.json({
28+
error: "Unauthorized",
29+
details: "Missing or malformed Authorization header.",
30+
});
31+
}
32+
33+
const token = authHeader.substring(7); // Remove "Bearer "
34+
35+
try {
36+
const verificationResult = await verify(token, {
37+
expectedRecipient: "curatefun.near",
38+
requireFullAccessKey: false,
39+
nonceMaxAge: 300000, // 5 mins
40+
});
41+
42+
accountId = verificationResult.accountId;
43+
c.set("accountId", accountId);
44+
await next();
45+
} catch (error) {
46+
console.error("Token verification error:", error);
47+
c.status(401);
48+
return c.json({
49+
error: "Unauthorized",
50+
details: "Invalid token signature or recipient.",
51+
});
52+
}
53+
};
54+
}

apps/api/src/routes/api/feeds.ts

Lines changed: 131 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import { Env } from "../../types/app";
33
import { badRequest } from "../../utils/error";
44
import { logger } from "../../utils/logger";
55
import { insertFeedSchema, updateFeedSchema } from "@curatedotfun/shared-db";
6+
import { z } from "zod"; // Added import for z
7+
import { zValidator } from "@hono/zod-validator"; // Added import for zValidator
8+
import { ModerationService } from "../../services/moderation.service"; // Added import for ModerationService
69

710
const feedsRoutes = new Hono<Env>();
811

@@ -25,17 +28,49 @@ feedsRoutes.get("/", async (c) => {
2528
* Create a new feed
2629
*/
2730
feedsRoutes.post("/", async (c) => {
31+
const accountId = c.get("accountId");
32+
if (!accountId) {
33+
return c.json(
34+
{ error: "Unauthorized. User must be logged in to create a feed." },
35+
401,
36+
);
37+
}
38+
2839
const body = await c.req.json();
29-
const validationResult = insertFeedSchema.safeParse(body);
40+
const partialValidationResult = insertFeedSchema
41+
.omit({ created_by: true })
42+
.safeParse(body);
3043

31-
if (!validationResult.success) {
32-
return badRequest(c, "Invalid feed data", validationResult.error.flatten());
44+
if (!partialValidationResult.success) {
45+
return badRequest(
46+
c,
47+
"Invalid feed data",
48+
partialValidationResult.error.flatten(),
49+
);
50+
}
51+
52+
const feedDataWithCreator = {
53+
...partialValidationResult.data,
54+
created_by: accountId,
55+
};
56+
57+
const finalValidationResult = insertFeedSchema.safeParse(feedDataWithCreator);
58+
if (!finalValidationResult.success) {
59+
logger.error(
60+
"Error in final validation after adding created_by",
61+
finalValidationResult.error,
62+
);
63+
return badRequest(
64+
c,
65+
"Internal validation error",
66+
finalValidationResult.error.flatten(),
67+
);
3368
}
3469

3570
const sp = c.get("sp");
3671
const feedService = sp.getFeedService();
3772
try {
38-
const newFeed = await feedService.createFeed(validationResult.data);
73+
const newFeed = await feedService.createFeed(finalValidationResult.data);
3974
return c.json(newFeed, 201);
4075
} catch (error) {
4176
logger.error("Error creating feed:", error);
@@ -66,16 +101,37 @@ feedsRoutes.get("/:feedId", async (c) => {
66101
* Update an existing feed
67102
*/
68103
feedsRoutes.put("/:feedId", async (c) => {
104+
const accountId = c.get("accountId");
105+
if (!accountId) {
106+
return c.json(
107+
{ error: "Unauthorized. User must be logged in to update a feed." },
108+
401,
109+
);
110+
}
111+
69112
const feedId = c.req.param("feedId");
113+
const sp = c.get("sp");
114+
const feedService = sp.getFeedService();
115+
116+
const canUpdate = await feedService.hasPermission(
117+
accountId,
118+
feedId,
119+
"update",
120+
);
121+
if (!canUpdate) {
122+
return c.json(
123+
{ error: "Forbidden. You do not have permission to update this feed." },
124+
403,
125+
);
126+
}
127+
70128
const body = await c.req.json();
71129
const validationResult = updateFeedSchema.safeParse(body);
72130

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

77-
const sp = c.get("sp");
78-
const feedService = sp.getFeedService();
79135
try {
80136
const updatedFeed = await feedService.updateFeed(
81137
feedId,
@@ -97,6 +153,14 @@ feedsRoutes.put("/:feedId", async (c) => {
97153
* Example: /api/feeds/solana/process?distributors=@curatedotfun/rss
98154
*/
99155
feedsRoutes.post("/:feedId/process", async (c) => {
156+
const accountId = c.get("accountId");
157+
if (!accountId) {
158+
return c.json(
159+
{ error: "Unauthorized. User must be logged in to process a feed." },
160+
401,
161+
);
162+
}
163+
100164
const sp = c.get("sp");
101165
const feedService = sp.getFeedService();
102166

@@ -127,9 +191,30 @@ feedsRoutes.post("/:feedId/process", async (c) => {
127191
* Delete a specific feed by its ID
128192
*/
129193
feedsRoutes.delete("/:feedId", async (c) => {
194+
const accountId = c.get("accountId");
195+
if (!accountId) {
196+
return c.json(
197+
{ error: "Unauthorized. User must be logged in to delete a feed." },
198+
401,
199+
);
200+
}
201+
130202
const feedId = c.req.param("feedId");
131203
const sp = c.get("sp");
132204
const feedService = sp.getFeedService();
205+
206+
const canDelete = await feedService.hasPermission(
207+
accountId,
208+
feedId,
209+
"delete",
210+
);
211+
if (!canDelete) {
212+
return c.json(
213+
{ error: "Forbidden. You do not have permission to delete this feed." },
214+
403,
215+
);
216+
}
217+
133218
try {
134219
const result = await feedService.deleteFeed(feedId);
135220
if (!result) {
@@ -142,4 +227,44 @@ feedsRoutes.delete("/:feedId", async (c) => {
142227
}
143228
});
144229

230+
const feedParamSchemaCanModerate = z.object({
231+
// Renamed to avoid conflict if other schemas exist
232+
feedId: z.string().min(1, "Feed ID is required"),
233+
});
234+
235+
feedsRoutes.get(
236+
"/:feedId/can-moderate",
237+
zValidator("param", feedParamSchemaCanModerate),
238+
async (c) => {
239+
const { feedId } = c.req.valid("param");
240+
const sp = c.get("sp");
241+
const moderationService =
242+
sp.getService<ModerationService>("moderationService");
243+
244+
const actingAccountId = c.get("accountId");
245+
246+
if (!actingAccountId) {
247+
return c.json({ canModerate: false, reason: "User not authenticated" });
248+
}
249+
250+
try {
251+
const canModerate =
252+
await moderationService.checkUserFeedModerationPermission(
253+
feedId,
254+
actingAccountId,
255+
);
256+
return c.json({ canModerate });
257+
} catch (error: any) {
258+
logger.error(
259+
`Error in /:feedId/can-moderate for feed ${feedId}, user ${actingAccountId}:`,
260+
error,
261+
);
262+
return c.json(
263+
{ canModerate: false, error: "Failed to check moderation permission" },
264+
500,
265+
);
266+
}
267+
},
268+
);
269+
145270
export { feedsRoutes };

0 commit comments

Comments
 (0)