Skip to content

Commit

Permalink
Merge pull request #1 from farcasterxyz/horsefacts/api-routes
Browse files Browse the repository at this point in the history
feat: naive API routes
  • Loading branch information
nickcherry authored Jan 25, 2024
2 parents 4e02903 + cf4bf05 commit 9790d91
Show file tree
Hide file tree
Showing 10 changed files with 436 additions and 0 deletions.
Binary file modified web/bun.lockb
Binary file not shown.
35 changes: 35 additions & 0 deletions web/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "quikcast",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"db:codegen": "kysely-codegen --dialect postgres --out-file ./src/lib/database/types.ts"
},
"dependencies": {
"@farcaster/auth-kit": "^0.0.39",
"@farcaster/core": "^0.13.4",
"next": "14.1.0",
"next-auth": "^4.24.5",
"react": "^18",
"react-dom": "^18"
},
"devDependencies": {
"@types/node": "^20",
"@types/pg": "^8.10.9",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10.0.1",
"eslint": "^8",
"eslint-config-next": "14.1.0",
"kysely": "^0.27.2",
"kysely-codegen": "^0.11.0",
"pg": "^8.11.3",
"postcss": "^8",
"tailwindcss": "^3.3.0",
"typescript": "^5"
}
}
13 changes: 13 additions & 0 deletions web/src/app/api/casts/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { type NextRequest, NextResponse } from 'next/server';

import { getCasts } from '@/lib/services/casts';

export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const fid = searchParams.get('fid');
if (!fid) {
return NextResponse.json({ error: 'fid is required' }, { status: 400 });
}
const casts = await getCasts(fid);
return NextResponse.json({ casts });
}
13 changes: 13 additions & 0 deletions web/src/app/api/feed/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { type NextRequest, NextResponse } from 'next/server';

import { getFeed } from '@/lib/services/feed';

export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const fid = searchParams.get('fid');
if (!fid) {
return NextResponse.json({ error: 'fid is required' }, { status: 400 });
}
const feed = await getFeed(fid);
return NextResponse.json({ feed });
}
13 changes: 13 additions & 0 deletions web/src/app/api/user/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { type NextRequest, NextResponse } from 'next/server';

import { getProfile } from '@/lib/services/user';

export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const fid = searchParams.get('fid');
if (!fid) {
return NextResponse.json({ error: 'fid is required' }, { status: 400 });
}
const profile = await getProfile(fid);
return NextResponse.json({ profile });
}
14 changes: 14 additions & 0 deletions web/src/lib/database/db.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Kysely, PostgresDialect } from 'kysely';
import { Pool } from 'pg';

import { DB } from './types';

const dialect = new PostgresDialect({
pool: new Pool({
connectionString: process.env.DATABASE_URL,
}),
});

export const db = new Kysely<DB>({
dialect,
});
204 changes: 204 additions & 0 deletions web/src/lib/database/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import type { ColumnType } from "kysely";

export type Generated<T> = T extends ColumnType<infer S, infer I, infer U>
? ColumnType<S, I | undefined, U>
: ColumnType<T, T | undefined, T>;

export type Int8 = ColumnType<string, bigint | number | string, bigint | number | string>;

export type Json = ColumnType<JsonValue, string, string>;

export type JsonArray = JsonValue[];

export type JsonObject = {
[K in string]?: JsonValue;
};

export type JsonPrimitive = boolean | number | string | null;

export type JsonValue = JsonArray | JsonObject | JsonPrimitive;

export type Timestamp = ColumnType<Date, Date | string, Date | string>;

export interface Casts {
created_at: Generated<Timestamp>;
deleted_at: Timestamp | null;
embeds: Generated<Json>;
fid: Int8;
hash: Buffer;
id: Generated<string>;
mentions: Generated<Json>;
mentions_positions: Generated<Json>;
parent_fid: Int8 | null;
parent_hash: Buffer | null;
parent_url: string | null;
root_parent_hash: Buffer | null;
root_parent_url: string | null;
text: string;
timestamp: Timestamp;
updated_at: Generated<Timestamp>;
}

export interface ChainEvents {
block_hash: Buffer;
block_number: Int8;
block_timestamp: Timestamp;
body: Json;
chain_id: Int8;
created_at: Generated<Timestamp>;
fid: Int8;
id: Generated<string>;
log_index: number;
raw: Buffer;
transaction_hash: Buffer;
transaction_index: number;
type: number;
}

export interface Fids {
chain_event_id: string;
created_at: Generated<Timestamp>;
custody_address: Buffer;
fid: Int8;
recovery_address: Buffer;
registered_at: Timestamp;
updated_at: Generated<Timestamp>;
}

export interface Fnames {
created_at: Generated<Timestamp>;
deleted_at: Timestamp | null;
fid: Int8;
id: Generated<string>;
registered_at: Timestamp;
type: number;
updated_at: Generated<Timestamp>;
username: string;
}

export interface Links {
created_at: Generated<Timestamp>;
deleted_at: Timestamp | null;
display_timestamp: Timestamp | null;
fid: Int8;
hash: Buffer;
id: Generated<string>;
target_fid: Int8;
timestamp: Timestamp;
type: string;
updated_at: Generated<Timestamp>;
}

export interface Messages {
body: Json;
created_at: Generated<Timestamp>;
deleted_at: Timestamp | null;
fid: Int8;
hash: Buffer;
hash_scheme: number;
id: Generated<string>;
pruned_at: Timestamp | null;
raw: Buffer;
revoked_at: Timestamp | null;
signature: Buffer;
signature_scheme: number;
signer: Buffer;
timestamp: Timestamp;
type: number;
updated_at: Generated<Timestamp>;
}

export interface Reactions {
created_at: Generated<Timestamp>;
deleted_at: Timestamp | null;
fid: Int8;
hash: Buffer;
id: Generated<string>;
target_cast_fid: Int8 | null;
target_cast_hash: Buffer | null;
target_url: string | null;
timestamp: Timestamp;
type: number;
updated_at: Generated<Timestamp>;
}

export interface Signers {
add_chain_event_id: string;
added_at: Timestamp;
created_at: Generated<Timestamp>;
fid: Int8;
id: Generated<string>;
key: Buffer;
key_type: number;
metadata: Json;
metadata_type: number;
remove_chain_event_id: string | null;
removed_at: Timestamp | null;
requester_fid: Int8;
updated_at: Generated<Timestamp>;
}

export interface StorageAllocations {
chain_event_id: string;
created_at: Generated<Timestamp>;
expires_at: Timestamp;
fid: Int8;
id: Generated<string>;
payer: Buffer;
rented_at: Timestamp;
units: number;
updated_at: Generated<Timestamp>;
}

export interface UserData {
created_at: Generated<Timestamp>;
deleted_at: Timestamp | null;
fid: Int8;
hash: Buffer;
id: Generated<string>;
timestamp: Timestamp;
type: number;
updated_at: Generated<Timestamp>;
value: string;
}

export interface UsernameProofs {
created_at: Generated<Timestamp>;
deleted_at: Timestamp | null;
fid: Int8;
id: Generated<string>;
owner: Buffer;
signature: Buffer;
timestamp: Timestamp;
type: number;
updated_at: Generated<Timestamp>;
username: string;
}

export interface Verifications {
block_hash: Buffer;
created_at: Generated<Timestamp>;
deleted_at: Timestamp | null;
fid: Int8;
hash: Buffer;
id: Generated<string>;
signature: Buffer;
signer_address: Buffer;
timestamp: Timestamp;
updated_at: Generated<Timestamp>;
}

export interface DB {
casts: Casts;
chain_events: ChainEvents;
fids: Fids;
fnames: Fnames;
links: Links;
messages: Messages;
reactions: Reactions;
signers: Signers;
storage_allocations: StorageAllocations;
user_data: UserData;
username_proofs: UsernameProofs;
verifications: Verifications;
}
61 changes: 61 additions & 0 deletions web/src/lib/services/casts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { sql } from 'kysely';

import { db } from '../database/db';

export function formatHash(hash: Buffer) {
return hash.toString('hex');
}

export async function getCasts(fid: string, limit = 100) {
const profile = db
.selectFrom('user_data')
.select([
'fid',
sql`MAX(CASE WHEN type = 1 THEN value ELSE NULL END)`.as('pfp_url'),
sql`MAX(CASE WHEN type = 2 THEN value ELSE NULL END)`.as('display_name'),
sql`MAX(CASE WHEN type = 3 THEN value ELSE NULL END)`.as('bio'),
sql`MAX(CASE WHEN type = 6 THEN value ELSE NULL END)`.as('username'),
])
.where('deleted_at', 'is', null)
.where('fid', '=', fid)
.groupBy('fid')
.as('profile');

const casts = await db
.selectFrom('casts')
.leftJoin(profile, 'profile.fid', 'casts.fid')
.select([
'casts.hash',
'casts.timestamp',
'casts.text',
'casts.embeds',
'casts.mentions',
'casts.mentions_positions',
'casts.fid',
'profile.pfp_url',
'profile.display_name',
'profile.bio',
'profile.username',
])
.where('casts.fid', '=', fid)
.where('casts.deleted_at', 'is', null)
.where('casts.parent_hash', 'is', null)
.orderBy('casts.timestamp', 'desc')
.limit(limit)
.execute();

return casts.map((row) => {
const { fid, pfp_url, display_name, bio, username, ...rest } = row;
return {
...rest,
hash: formatHash(row.hash),
user: {
fid,
pfp_url,
display_name,
bio,
username,
},
};
});
}
Loading

0 comments on commit 9790d91

Please sign in to comment.