Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
30c1be6
feat: add protocol support for register, transfer and accept
jeronimoalbi Nov 12, 2025
ad49c3b
feat: add API endpoints for register, transfer and accept
jeronimoalbi Nov 12, 2025
cd6162d
feat: change handle register to remove previous handle
jeronimoalbi Nov 13, 2025
32bada1
fix: correct issue in reader that led to always start from 1st block
jeronimoalbi Nov 13, 2025
58aeae0
chore: remove one API URL from reader Docker compose file
jeronimoalbi Nov 13, 2025
d964956
feat: remove prepared statements and change code for consistency
jeronimoalbi Nov 13, 2025
754e0f5
fix: remove previous handle transfer if it exists
jeronimoalbi Nov 13, 2025
9cdf188
feat: support search posts by user handle
jeronimoalbi Nov 17, 2025
9a468eb
feat: add user handle to feed, follow and post endpoints
jeronimoalbi Nov 17, 2025
6b50ff7
feat: add user handle to posts endpoint
jeronimoalbi Nov 17, 2025
62bcb9b
feat: add user handle display support protocol
jeronimoalbi Nov 17, 2025
50c3dd4
feat: add display field to API endpoints
jeronimoalbi Nov 17, 2025
39c2632
feat: add notification for registration of a user handle already taken
jeronimoalbi Nov 18, 2025
e356f0a
feat: add API endpoint to get user handle by name or address
jeronimoalbi Nov 18, 2025
8464e85
refactor: change protocol names to be more explicit
jeronimoalbi Nov 19, 2025
f9cf2f7
chore: use uppercase for global consts
jeronimoalbi Nov 19, 2025
9447752
refactor: change HandleTable name to AccountTable
jeronimoalbi Nov 19, 2025
bf956b9
refactor: update user handle protocol features for account table
jeronimoalbi Nov 20, 2025
26ef719
feat: add notification when a handle is transfered to a user
jeronimoalbi Nov 20, 2025
8e4e07c
Merge branch 'main' into feat/username-handle-support
jeronimoalbi Nov 24, 2025
f576d6b
fix: change Tiltfile to reload on code changes
jeronimoalbi Nov 24, 2025
ff9eb48
chore: change reader docker compose to use testnet API by default
jeronimoalbi Nov 24, 2025
eb2fef3
chore: update dockerignore
jeronimoalbi Nov 25, 2025
25e0350
chore: allow requests to `https://*.allinbits.services`
jeronimoalbi Nov 25, 2025
1a9cc2f
feat: change UI to render user handle when available
jeronimoalbi Nov 25, 2025
4ea2887
feat: add tooltip to user address or handle
jeronimoalbi Nov 25, 2025
a94360d
refactor: change handle API endpoint to account
jeronimoalbi Nov 25, 2025
278c79f
fix: correct issue with account API endpoint
jeronimoalbi Nov 25, 2025
29025e3
chore: reduce handle length to 25 characters
jeronimoalbi Nov 26, 2025
58fb139
feat: add `useAcount()` composable
jeronimoalbi Nov 26, 2025
5686a5b
feat: add user handle to profile view and wallet connect button
jeronimoalbi Nov 26, 2025
df24fcf
chore: add missing import
jeronimoalbi Nov 26, 2025
ce50231
fix: correct following API endpoint issue with user handles
jeronimoalbi Nov 26, 2025
60ff6c2
feat: add views and dialogs to register a new account handle
jeronimoalbi Nov 27, 2025
8017c27
fix: correct TS issues
jeronimoalbi Nov 27, 2025
2d589da
feat: change frontend to validate account handle registration
jeronimoalbi Nov 27, 2025
1462ac5
chore: add handle name to the registration error notification
jeronimoalbi Nov 29, 2025
6eedb03
chore: remove error when `useAccount()` fails to find an account
jeronimoalbi Nov 29, 2025
090030d
feat: change handle to appear below account addresses
jeronimoalbi Nov 29, 2025
4b2016f
fix: correct TS issues
jeronimoalbi Nov 29, 2025
f04a21b
feat: allow configuring the required fee to register a handle
jeronimoalbi Nov 29, 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
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
**/node_modules
**/dist
**/dist/*
.husky/
.vscode/
4 changes: 2 additions & 2 deletions Tiltfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ docker_compose('docker-compose.yml')
docker_build('ditherchat/api-main', '.',
dockerfile = './packages/api-main/Dockerfile',
live_update = [
sync('./packages/api-main/src', '/app'),
sync('./packages/api-main/src', '/app/packages/api-main/src'),
run('bun install', trigger='package.json'),
restart_container(),
])

docker_build('ditherchat/reader-main', '.',
dockerfile = './packages/reader-main/Dockerfile',
live_update = [
sync('./packages/reader-main/src', '/app'),
sync('./packages/reader-main/src', '/app/packages/reader-main/src'),
run('bun install', trigger='package.json'),
restart_container(),
])
Expand Down
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ services:
RECEIVER: atone1uq6zjslvsa29cy6uu75y8txnl52mw06j6fzlep
API_ROOT: 'http://api-main:3000/v1'
AUTH: dev
MIN_REGISTER_HANDLE_FEE: '0.000001'

# Uncomment to enable fast sync
# ECLESIA_GRAPHQL_ENDPOINT: "https://graphql-atomone-testnet-1.allinbits.services/v1/graphql"
# ECLESIA_GRAPHQL_SECRET: ""
Expand Down
6 changes: 4 additions & 2 deletions packages/api-main/drizzle/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import pg from 'pg';

dotenv.config();

export type DbClient = ReturnType<typeof drizzle>;

const { Pool } = pg;

let db: ReturnType<typeof drizzle>;
let db: DbClient;

export function getDatabase() {
export function getDatabase(): DbClient {
if (!db) {
const client = new Pool({
connectionString: process.env.PG_URI!,
Expand Down
42 changes: 40 additions & 2 deletions packages/api-main/drizzle/schema.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,36 @@
import { sql } from 'drizzle-orm';
import { bigint, boolean, index, integer, pgEnum, pgTable, primaryKey, serial, text, timestamp, varchar } from 'drizzle-orm/pg-core';
import { bigint, boolean, index, integer, pgEnum, pgTable, primaryKey, serial, text, timestamp, unique, varchar } from 'drizzle-orm/pg-core';

const MEMO_LENGTH = 512;

export const AccountTable = pgTable(
'account',
{
address: varchar({ length: 44 }).primaryKey(),
handle: varchar({ length: 25 }),
display: varchar({ length: 128 }),
},
t => [
unique('account_handle_idx').on(t.handle),
index('account_display_idx').on(t.display),
],
);

export const HandleTransferTable = pgTable(
'handle_transfer',
{
hash: varchar({ length: 64 }).notNull(),
name: varchar({ length: 32 }).notNull(),
from_address: varchar({ length: 44 }).notNull(),
to_address: varchar({ length: 44 }).notNull(),
accepted: boolean().default(false).notNull(),
timestamp: timestamp({ withTimezone: true }).notNull(),
},
t => [
index('handle_transfer_to_idx').on(t.name, t.to_address),
],
);

export const FeedTable = pgTable(
'feed',
{
Expand Down Expand Up @@ -129,7 +157,15 @@ export const ModeratorTable = pgTable('moderators', {
deleted_at: timestamp({ withTimezone: true }),
});

export const notificationTypeEnum = pgEnum('notification_type', ['like', 'dislike', 'flag', 'follow', 'reply']);
export const notificationTypeEnum = pgEnum('notification_type', [
'like',
'dislike',
'flag',
'follow',
'reply',
'registerHandle',
'transferHandle',
]);

export const NotificationTable = pgTable(
'notifications',
Expand Down Expand Up @@ -166,4 +202,6 @@ export const tables = [
'state',
'authrequests',
'ratelimits',
'handle',
'handle_transfer',
];
36 changes: 36 additions & 0 deletions packages/api-main/src/gets/account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { Gets } from '@atomone/dither-api-types';

import { eq, or, sql } from 'drizzle-orm';

import { getDatabase } from '../../drizzle/db';
import { AccountTable } from '../../drizzle/schema';

const statement = getDatabase()
.select()
.from(AccountTable)
.where(
or(
eq(AccountTable.handle, sql.placeholder('handle')),
eq(AccountTable.address, sql.placeholder('address')),
),
)
.prepare('stmnt_get_handle');

export async function Account(query: Gets.AccountQuery) {
const { address, handle } = query;
if (!address && !handle) {
return { status: 400, error: 'handle or address is required' };
}

try {
const [account] = await statement.execute({ address, handle });
if (!account) {
return { status: 404, rows: [] };
}

return { status: 200, rows: [account] };
} catch (error) {
console.error(error);
return { error: 'failed to read data from database' };
}
}
11 changes: 8 additions & 3 deletions packages/api-main/src/gets/feed.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import type { Gets } from '@atomone/dither-api-types';

import { and, count, desc, gte, isNull, sql } from 'drizzle-orm';
import { and, count, desc, eq, getTableColumns, gte, isNull, sql } from 'drizzle-orm';

import { getDatabase } from '../../drizzle/db';
import { FeedTable } from '../../drizzle/schema';
import { AccountTable, FeedTable } from '../../drizzle/schema';

const statement = getDatabase()
.select()
.select({
...getTableColumns(FeedTable),
author_handle: AccountTable.handle,
author_display: AccountTable.display,
})
.from(FeedTable)
.leftJoin(AccountTable, eq(FeedTable.author, AccountTable.address))
.limit(sql.placeholder('limit'))
.offset(sql.placeholder('offset'))
.where(
Expand Down
10 changes: 8 additions & 2 deletions packages/api-main/src/gets/followers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@ import type { Gets } from '@atomone/dither-api-types';
import { and, desc, eq, isNull, sql } from 'drizzle-orm';

import { getDatabase } from '../../drizzle/db';
import { FollowsTable } from '../../drizzle/schema';
import { AccountTable, FollowsTable } from '../../drizzle/schema';

const statementGetFollowers = getDatabase()
.select({ address: FollowsTable.follower, hash: FollowsTable.hash })
.select({
address: FollowsTable.follower,
handle: AccountTable.handle,
display: AccountTable.display,
hash: FollowsTable.hash,
})
.from(FollowsTable)
.leftJoin(AccountTable, eq(AccountTable.address, FollowsTable.follower))
.where(and(eq(FollowsTable.following, sql.placeholder('following')), isNull(FollowsTable.removed_at)))
.limit(sql.placeholder('limit'))
.offset(sql.placeholder('offset'))
Expand Down
10 changes: 8 additions & 2 deletions packages/api-main/src/gets/following.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@ import type { Gets } from '@atomone/dither-api-types';
import { and, desc, eq, isNull, sql } from 'drizzle-orm';

import { getDatabase } from '../../drizzle/db';
import { FollowsTable } from '../../drizzle/schema';
import { AccountTable, FollowsTable } from '../../drizzle/schema';

const statementGetFollowing = getDatabase()
.select({ address: FollowsTable.following, hash: FollowsTable.hash })
.select({
address: FollowsTable.following,
handle: AccountTable.handle,
display: AccountTable.display,
hash: FollowsTable.hash,
})
.from(FollowsTable)
.leftJoin(AccountTable, eq(AccountTable.address, FollowsTable.following))
.where(and(eq(FollowsTable.follower, sql.placeholder('follower')), isNull(FollowsTable.removed_at)))
.limit(sql.placeholder('limit'))
.offset(sql.placeholder('offset'))
Expand Down
1 change: 1 addition & 0 deletions packages/api-main/src/gets/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './account';
export * from './authVerify';
export * from './dislikes';
export * from './feed';
Expand Down
18 changes: 14 additions & 4 deletions packages/api-main/src/gets/post.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
import type { Gets } from '@atomone/dither-api-types';

import { and, eq, isNull, sql } from 'drizzle-orm';
import { and, eq, getTableColumns, isNull, sql } from 'drizzle-orm';

import { getDatabase } from '../../drizzle/db';
import { FeedTable } from '../../drizzle/schema';
import { AccountTable, FeedTable } from '../../drizzle/schema';

const statementGetPost = getDatabase()
.select()
.select({
...getTableColumns(FeedTable),
author_handle: AccountTable.handle,
author_display: AccountTable.display,
})
.from(FeedTable)
.leftJoin(AccountTable, eq(FeedTable.author, AccountTable.address))
.where(and(isNull(FeedTable.removed_at), eq(FeedTable.hash, sql.placeholder('hash'))))
.prepare('stmnt_get_post');

const statementGetReply = getDatabase()
.select()
.select({
...getTableColumns(FeedTable),
author_handle: AccountTable.handle,
author_display: AccountTable.display,
})
.from(FeedTable)
.leftJoin(AccountTable, eq(FeedTable.author, AccountTable.address))
.where(and(isNull(FeedTable.removed_at), eq(FeedTable.hash, sql.placeholder('hash')), eq(FeedTable.post_hash, sql.placeholder('post_hash'))))
.prepare('stmnt_get_reply');

Expand Down
18 changes: 14 additions & 4 deletions packages/api-main/src/gets/posts.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import type { Gets } from '@atomone/dither-api-types';

import { and, desc, eq, gte, inArray, isNull, sql } from 'drizzle-orm';
import { and, desc, eq, getTableColumns, gte, inArray, isNull, sql } from 'drizzle-orm';

import { getDatabase } from '../../drizzle/db';
import { FeedTable, FollowsTable } from '../../drizzle/schema';
import { AccountTable, FeedTable, FollowsTable } from '../../drizzle/schema';

const statement = getDatabase()
.select()
.select({
...getTableColumns(FeedTable),
author_handle: AccountTable.handle,
author_display: AccountTable.display,
})
.from(FeedTable)
.leftJoin(AccountTable, eq(FeedTable.author, AccountTable.address))
.where(
and(
eq(FeedTable.author, sql.placeholder('author')),
Expand Down Expand Up @@ -48,8 +53,13 @@ export async function Posts(query: Gets.PostsQuery) {
}

const followingPostsStatement = getDatabase()
.select()
.select({
...getTableColumns(FeedTable),
author_handle: AccountTable.handle,
author_display: AccountTable.display,
})
.from(FeedTable)
.leftJoin(AccountTable, eq(FeedTable.author, AccountTable.address))
.where(
and(
inArray(FeedTable.author, getDatabase()
Expand Down
26 changes: 20 additions & 6 deletions packages/api-main/src/gets/search.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { Gets } from '@atomone/dither-api-types';

import { and, desc, gte, ilike, inArray, isNull, or, sql } from 'drizzle-orm';
import { and, desc, eq, getTableColumns, gte, ilike, inArray, isNull, or, sql } from 'drizzle-orm';

import { getDatabase } from '../../drizzle/db';
import { FeedTable } from '../../drizzle/schema';
import { AccountTable, FeedTable } from '../../drizzle/schema';

export async function Search(query: Gets.SearchQuery) {
try {
Expand All @@ -22,12 +22,26 @@ export async function Search(query: Gets.SearchQuery) {
const matchedAuthors = await getDatabase()
.selectDistinct({ author: FeedTable.author })
.from(FeedTable)
.where(and(ilike(FeedTable.author, `%${query.text}%`), isNull(FeedTable.removed_at)));
.leftJoin(AccountTable, eq(FeedTable.author, AccountTable.address))
.where(
and(
or(
eq(FeedTable.author, query.text.toLowerCase()), // Exact address
ilike(AccountTable.handle, `%${query.text}%`), // Registered handle (partial match)
),
isNull(FeedTable.removed_at),
),
);
const matchedAuthorAddresses = matchedAuthors.map(a => a.author);

const matchedPosts = await getDatabase()
.select()
.select({
...getTableColumns(FeedTable),
author_handle: AccountTable.handle,
author_display: AccountTable.display,
})
.from(FeedTable)
.leftJoin(AccountTable, eq(FeedTable.author, AccountTable.address))
.where(
and(
or(
Expand All @@ -40,8 +54,8 @@ export async function Search(query: Gets.SearchQuery) {
)
.limit(100)
.offset(0)
.orderBy(desc(FeedTable.timestamp))
.execute();
.orderBy(desc(FeedTable.timestamp));

return { status: 200, rows: [...matchedPosts], users: matchedAuthorAddresses };
} catch (error) {
console.error(error);
Expand Down
Loading
Loading