diff --git a/apps/web/src/actions/index.ts b/apps/web/src/actions/index.ts
index 6cd4273..dfcfd1f 100644
--- a/apps/web/src/actions/index.ts
+++ b/apps/web/src/actions/index.ts
@@ -27,9 +27,10 @@ export const server = {
tag: z.string().default("tech"),
}),
handler: async (input, ctx) => {
- if (!ctx.locals.mayClassify) {
- throw new Error("Not authorized to classify");
- }
+ // TODO: basic auth not sent with xhr
+ // if (!ctx.locals.mayClassify) {
+ // throw new Error("Not authorized to classify");
+ // }
await classifyPost(ctx.locals.at, {
postUri: input.postUri,
algorithm: "manual",
diff --git a/apps/web/src/middleware.ts b/apps/web/src/middleware.ts
index 6949c39..e2501e3 100644
--- a/apps/web/src/middleware.ts
+++ b/apps/web/src/middleware.ts
@@ -56,7 +56,6 @@ function isAuthorized(context: APIContext): boolean {
return true;
}
}
- console.log("not authorized", basicAuth);
return false;
}
diff --git a/apps/web/src/pages/admin/feed/[rkey]/index.astro b/apps/web/src/pages/admin/feed/[rkey]/index.astro
index 556b87c..71f3a6f 100644
--- a/apps/web/src/pages/admin/feed/[rkey]/index.astro
+++ b/apps/web/src/pages/admin/feed/[rkey]/index.astro
@@ -10,10 +10,11 @@ if (!feed) {
console.log("404");
return Astro.redirect("/404");
}
-const postUris = await feed.handler({
+const res = await feed.handler({
ctx: Astro.locals.at,
actorDid: DEFAULT_FEED_ACTOR,
});
+const postUris = res.feed.map((p) => p.post);
const posts = await getPublicPosts(postUris);
const mayClassify = Astro.locals.mayClassify;
---
diff --git a/apps/web/src/pages/index.astro b/apps/web/src/pages/index.astro
index 608bef4..94e9917 100644
--- a/apps/web/src/pages/index.astro
+++ b/apps/web/src/pages/index.astro
@@ -2,7 +2,7 @@
import Layout from "~astro/layout.astro";
// Welcome to Astro! Wondering what to do next? Check out the Astro documentation at https://docs.astro.build
// Don't want to use any of this? Delete everything in this file, the `assets`, `components`, and `layouts` directories, and start fresh.
-return Astro.redirect("/feed");
+//return Astro.redirect("/feed"); // TODO: When session is ready
---
- Nothing here
+ Nothing here yet
diff --git a/apps/web/src/pages/xrpc/app.bsky.feed.getFeedSkeleton.ts b/apps/web/src/pages/xrpc/app.bsky.feed.getFeedSkeleton.ts
index 81784b3..cd5b161 100644
--- a/apps/web/src/pages/xrpc/app.bsky.feed.getFeedSkeleton.ts
+++ b/apps/web/src/pages/xrpc/app.bsky.feed.getFeedSkeleton.ts
@@ -9,12 +9,13 @@ export const GET: APIRoute = async ({ request: req, locals }) => {
// return new Response(
// "No authorization header found. This endpoint requires it."
// );
-
- const FeedParam = new URL(req.url).searchParams.get("feed");
- if (!FeedParam) {
+ const url = new URL(req.url);
+ const feedParam = url.searchParams.get("feed");
+ if (!feedParam) {
return HTTPError("no feed requested");
}
- const feedUri = new AtUri(FeedParam);
+ const cursor = url.searchParams.get("cursor");
+ const feedUri = new AtUri(feedParam);
// pathname looks like this: /app.bsky.feed.generator/tech-following
const rkeyMatch = feedUri.pathname.match(/\/([^/]+)$/);
if (!rkeyMatch || rkeyMatch.length < 2) {
@@ -26,13 +27,15 @@ export const GET: APIRoute = async ({ request: req, locals }) => {
if (!feed) return HTTPError("feed not found");
const actor = jwt?.iss ?? "did:plc:rrrwbar3wv576qpsymwey5p5"; // defaults to me for testing
- const posts = (await feed.handler({ ctx, actorDid: actor })).map((p) => ({
- post: p,
- }));
+ const res = await feed.handler({
+ ctx,
+ actorDid: actor,
+ cursor: cursor ?? undefined,
+ });
return Response.json({
// Possible to inject posts here later (ads, announcements, etc)
- feed: [...posts],
+ ...res,
} satisfies AppBskyFeedGetFeedSkeleton.Response["data"]);
};
diff --git a/packages/atproto/domain/get-tech-all-feed.ts b/packages/atproto/domain/get-tech-all-feed.ts
index f1b570d..f8e1b8b 100644
--- a/packages/atproto/domain/get-tech-all-feed.ts
+++ b/packages/atproto/domain/get-tech-all-feed.ts
@@ -1,23 +1,32 @@
-import { and, desc, eq, gte } from "drizzle-orm/expressions";
+import { and, desc, eq, gt, gte } from "drizzle-orm/expressions";
import { postScores, postTable } from "../db/schema";
-import type { FeedHandlerArgs } from "../feeds";
+import type { FeedHandlerArgs, FeedHandlerOutput } from "../feeds";
+import { fromCursor, toCursor } from "../helpers/cursor";
export async function getTechAllFeed(
args: FeedHandlerArgs
-): Promise> {
+): Promise {
const { ctx, cursor } = args;
const posts = await ctx.db
- .select({ id: postTable.id })
+ .select({ id: postTable.id, created: postTable.created })
.from(postTable)
.innerJoin(
postScores,
- and(eq(postScores.postId, postTable.id), eq(postScores.tagId, "tech"))
+ and(
+ eq(postScores.postId, postTable.id),
+ eq(postScores.tagId, "tech"),
+ cursor ? gt(postTable.created, fromCursor(cursor)) : undefined
+ )
)
.where(gte(postScores.avgScore, 80))
.orderBy(desc(postTable.created))
.limit(30);
- return posts.map((post) => post.id);
+ return {
+ feed: posts.map((post) => ({ post: post.id })),
+ cursor:
+ posts.length > 0 ? toCursor(posts[posts.length - 1].created) : undefined,
+ };
}
diff --git a/packages/atproto/domain/get-tech-following-feed.ts b/packages/atproto/domain/get-tech-following-feed.ts
index 984f93b..4f577f1 100644
--- a/packages/atproto/domain/get-tech-following-feed.ts
+++ b/packages/atproto/domain/get-tech-following-feed.ts
@@ -1,12 +1,13 @@
-import { and, desc, eq, gte } from "drizzle-orm/expressions";
+import { and, desc, eq, gt, gte } from "drizzle-orm/expressions";
import { followTable, postScores, postTable } from "../db/schema";
-import type { FeedHandlerArgs } from "../feeds";
+import type { FeedHandlerArgs, FeedHandlerOutput } from "../feeds";
+import { fromCursor, toCursor } from "../helpers/cursor";
import { getOrUpdateFollows } from "./get-or-update-follows";
export async function getTechFollowingFeed(
args: FeedHandlerArgs
-): Promise> {
- const { ctx, actorDid } = args;
+): Promise {
+ const { ctx, actorDid, cursor } = args;
await getOrUpdateFollows(ctx, actorDid);
@@ -17,16 +18,26 @@ export async function getTechFollowingFeed(
.as("fls");
const posts = await ctx.db
- .select({ id: postTable.id })
+ .select({ id: postTable.id, created: postTable.created })
.from(postTable)
.innerJoin(fls, eq(postTable.authorId, fls.follows))
.innerJoin(
postScores,
- and(eq(postScores.postId, postTable.id), eq(postScores.tagId, "tech"))
+ and(
+ eq(postScores.postId, postTable.id),
+ eq(postScores.tagId, "tech"),
+ cursor ? gt(postTable.created, fromCursor(cursor)) : undefined
+ )
)
.where(gte(postScores.avgScore, 80)) // TODO: Raise this to 80
.orderBy(desc(postTable.created))
.limit(50);
- return posts.map((post) => post.id);
+ return {
+ feed: posts.map((p) => ({
+ post: p.id,
+ })),
+ cursor:
+ posts.length > 0 ? toCursor(posts[posts.length - 1].created) : undefined,
+ };
}
diff --git a/packages/atproto/feeds/index.ts b/packages/atproto/feeds/index.ts
index 1781a12..c1f0b4f 100644
--- a/packages/atproto/feeds/index.ts
+++ b/packages/atproto/feeds/index.ts
@@ -1,4 +1,7 @@
-import type { AppBskyFeedGenerator } from "@atproto/api";
+import type {
+ AppBskyFeedGenerator,
+ AppBskyFeedGetFeedSkeleton,
+} from "@atproto/api";
import { config } from "../config";
import type { AtContext } from "../context";
import { getTechAllFeed } from "../domain/get-tech-all-feed";
@@ -12,10 +15,12 @@ export type FeedHandlerArgs = {
limit?: number;
};
+export type FeedHandlerOutput = AppBskyFeedGetFeedSkeleton.Response["data"];
+
export type FeedDefinition = {
rkey: string; // feed-id (affects url)
record: AppBskyFeedGenerator.Record;
- handler: (args: FeedHandlerArgs) => Promise>;
+ handler: (args: FeedHandlerArgs) => Promise;
};
export const feeds: Array = [
diff --git a/packages/atproto/helpers/cursor.ts b/packages/atproto/helpers/cursor.ts
new file mode 100644
index 0000000..b1fcbc1
--- /dev/null
+++ b/packages/atproto/helpers/cursor.ts
@@ -0,0 +1,9 @@
+export function toCursor(d: Date): string {
+ // Convert date to unixtime string, in seconds
+ return Math.floor(d.getTime() / 1000).toString();
+}
+
+export function fromCursor(c: string): Date {
+ // Convert cursor string (unixtime) to Date
+ return new Date(parseInt(c) * 1000);
+}