decentralised message store

Compare changes

Choose any two refs to compare.

Changed files
+105 -4
.github
workflows
src
db
schema
lib
handlers
types
utils
routes
xrpc
_health
systems.gmstn.development.shard.getOwner
server
+7
src/lib/utils/atproto.ts
···
export const atUriRegexp =
/^at:\/\/([a-zA-Z0-9._:%-]+)(?:\/([a-zA-Z0-9-.]+)(?:\/([a-zA-Z0-9._~:@!$&%')(*+,;=-]+))?)?(?:#(\/[a-zA-Z0-9._~:@!$&%')(*+,;=\-[\]/\\]*))?$/;
+
export const atUriToString = ({ authority, collection, rKey }: AtUri) => {
+
let result = `at://${authority}`;
+
result += collection ? `/${collection}` : "";
+
result += rKey ? `/${rKey}` : "";
+
return result;
+
};
+
export const stringToAtUri = (str: string): Result<AtUri, unknown> => {
const isValidAtUri = atUriRegexp.test(str);
if (!isValidAtUri)
-2
src/db/schema/messages.ts
···
export const messagesTable = sqliteTable(
"messages",
{
-
// we do incrementing numbers for now but for goodness sake we need to come up with something better
-
// TODO: id by snowflakes or something more sane.
id: text("id").primaryKey(),
channelAtUri: text("channel_at_uri"),
authorDid: text("author_did").notNull(),
+9
src/lib/handlers/getOwnerDid.ts
···
+
import { OWNER_DID } from "@/lib/env";
+
import { getRegistrationState } from "@/lib/state";
+
import type { RouteHandler } from "@/lib/types/routes";
+
import { newSuccessResponse } from "@/lib/utils/http/responses";
+
+
export const getOwnerHandler: RouteHandler = () => {
+
const { registered } = getRegistrationState();
+
return newSuccessResponse({ registered, ownerDid: OWNER_DID });
+
};
+11 -1
src/lib/types/http/responses.ts
···
import { sessionInfoSchema } from "@/lib/sessions";
+
import { didSchema } from "@/lib/types/atproto";
import { httpResponseErrorInfoSchema } from "@/lib/types/http/errors";
import { z } from "zod";
···
});
export type HandshakeResponse = z.infer<typeof handshakeResponseSchema>;
-
export const httpResponseDataSchema = z.union([handshakeResponseSchema]);
+
export const getOwnerDidResponseSchema = z.object({
+
registered: z.boolean(),
+
ownerDid: didSchema,
+
});
+
export type GetOwnerDidResponse = z.infer<typeof getOwnerDidResponseSchema>;
+
+
export const httpResponseDataSchema = z.union([
+
handshakeResponseSchema,
+
getOwnerDidResponseSchema,
+
]);
export type HttpResponseData = z.infer<typeof httpResponseDataSchema>;
const httpResponseBaseSchema = z.object({
+5
src/routes/index.ts
···
import { didWebDocRoute } from "@/routes/dot-well-known/did-dot-json/route";
import { handshakeRoute } from "@/routes/handshake/route";
import { indexRoute } from "@/routes/route";
+
import { healthRoute } from "@/routes/xrpc/_health/route";
+
import { systemsGmstnDevelopmentShardGetOwnerRoute } from "@/routes/xrpc/systems.gmstn.development.shard.getOwner/route";
export const routes: Record<string, Route | WsRoute> = {
"/": indexRoute,
"/.well-known/did.json": didWebDocRoute,
"/handshake": handshakeRoute,
"/connect": connectRoute,
+
"/xrpc/_health": healthRoute,
+
"/xrpc/systems.gmstn.development.shard.getOwner":
+
systemsGmstnDevelopmentShardGetOwnerRoute,
};
+10
src/routes/xrpc/_health/route.ts
···
+
import type { Route } from "@/lib/types/routes";
+
+
export const healthRoute: Route = {
+
method: "GET",
+
handler: () => {
+
return new Response("this shard is running at 0.0.1", {
+
headers: { "content-type": "text/plain; charset=utf-8" },
+
});
+
},
+
};
+7
src/routes/xrpc/systems.gmstn.development.shard.getOwner/route.ts
···
+
import { getOwnerHandler } from "@/lib/handlers/getOwnerDid";
+
import type { Route } from "@/lib/types/routes";
+
+
export const systemsGmstnDevelopmentShardGetOwnerRoute: Route = {
+
method: "GET",
+
handler: getOwnerHandler,
+
};
+5
src/server/index.ts
···
import websocket from "@fastify/websocket";
+
import cors from "@fastify/cors";
import Fastify from "fastify";
export const setupServer = async () => {
···
logger: true,
});
+
await fastify.register(cors, {
+
origin: true,
+
});
+
await fastify.register(websocket);
return fastify;
+1 -1
src/lib/handlers/connect.ts
···
if (!messagesResult.ok) {
console.error(messagesResult.error);
throw new Error(
-
"Channel history function returned 0 results.",
+
"Something went wrong when trying to get messages from the DB. Check the schema and the connection?",
);
}
const historyMessage: HistoryMessage = {
+1
.gitignore
···
/node_modules
/dist
.env
+
.docker.env
*.tsbuildinfo
+2
shell.nix
···
pnpm,
typescript,
typescript-language-server,
+
docker,
callPackage,
}:
···
pnpm
typescript
typescript-language-server
+
docker
];
shellHook = ''
+47
.github/workflows/push-image.yaml
···
+
name: Build and Push Docker Image
+
+
on:
+
push:
+
tags:
+
- "v*"
+
+
env:
+
REGISTRY: ghcr.io
+
IMAGE_NAME: ${{ github.repository }}
+
+
jobs:
+
build-and-push:
+
runs-on: ubuntu-latest
+
permissions:
+
contents: read
+
packages: write
+
+
steps:
+
- name: Checkout repository
+
uses: actions/checkout@v4
+
+
- name: Log in to GitHub Container Registry
+
uses: docker/login-action@v3
+
with:
+
registry: ${{ env.REGISTRY }}
+
username: ${{ github.actor }}
+
password: ${{ secrets.GITHUB_TOKEN }}
+
+
- name: Extract metadata (tags, labels)
+
id: meta
+
uses: docker/metadata-action@v5
+
with:
+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
+
tags: |
+
type=semver,pattern={{version}}
+
type=semver,pattern={{major}}.{{minor}}
+
type=semver,pattern={{major}}
+
type=raw,value=latest
+
+
- name: Build and push Docker image
+
uses: docker/build-push-action@v5
+
with:
+
context: .
+
push: true
+
tags: ${{ steps.meta.outputs.tags }}
+
labels: ${{ steps.meta.outputs.labels }}