Skip to main content

Overview

The forum system includes specialized integration for DUNA (Decentralized Unincorporated Nonprofit Association) with dedicated components for quarterly reports and document management.
DUNA is a legal framework for DAOs to operate as unincorporated nonprofit associations. The forum provides specialized tools for DUNA compliance and reporting.

DUNA Category

Category Configuration

DUNA categories are marked with a special flag:
interface ForumCategory {
  id: number;
  name: string;
  description?: string;
  isDuna: boolean;          // Marks category as DUNA-specific
  adminOnlyTopics: boolean; // Only admins can create topics
  archived: boolean;
}

Get DUNA Category

src/lib/actions/forum/categories.ts
export async function getDunaCategoryId() {
  const category = await prismaWeb2Client.forumCategory.findFirst({
    where: {
      dao_slug: slug,
      isDuna: true,
    },
    select: { id: true },
  });

  return category?.id;
}

Quarterly Reports

Quarterly reports are forum topics in the DUNA category with specialized handling.

Create Quarterly Report

Reports are created as regular topics but in the DUNA category:
import { useForum } from '@/hooks/useForum';

function CreateQuarterlyReport() {
  const { createTopic } = useForum();
  const dunaCategoryId = await getDunaCategoryId();
  
  const handleCreateReport = async () => {
    const result = await createTopic(
      'Q1 2024 Quarterly Report',
      reportContent,
      dunaCategoryId // DUNA category ID
    );
    
    if (result?.success) {
      console.log('Quarterly report created');
    }
  };
}

Report Structure

Quarterly reports follow a standard structure:
# Q1 2024 Quarterly Report

## Financial Summary
- Revenue: $XXX,XXX
- Expenses: $XX,XXX
- Treasury Balance: $XXX,XXX

## Activities
- Governance proposals: X
- Community events: X
- Development milestones: X

## Compliance
- Tax status: Current
- Legal filings: Up to date
- Audit: Completed

## Attachments
- Financial Statement (PDF)
- Audit Report (PDF)
- Activity Log (CSV)

Comment on Reports

Community members can comment on quarterly reports:
import { useForum } from '@/hooks/useForum';

function ReportComments({ reportTopicId }) {
  const { createPost } = useForum();
  
  const handleComment = async (content: string) => {
    const result = await createPost(
      reportTopicId,
      content,
      null // No parent (top-level comment)
    );
    
    return result;
  };
}

Document Management

Upload DUNA Documents

DUNA documents are attached to the DUNA category:
src/lib/actions/forum/attachments.ts
export async function uploadDocumentFromBase64(
  base64Data: string,
  fileName: string,
  contentType: string,
  address: string,
  signature: string,
  message: string,
  categoryId: number // DUNA category ID
) {
  // Verify signature
  const isValid = await verifyMessage({
    address: address as `0x${string}`,
    message,
    signature: signature as `0x${string}`,
  });

  if (!isValid) {
    return { success: false, error: 'Invalid signature' };
  }

  // Convert and upload to IPFS
  const buffer = Buffer.from(base64Data.split(',')[1], 'base64');
  const result = await uploadFileToPinata(buffer, {
    name: fileName,
    keyvalues: {
      type: 'forum-document',
      originalName: fileName,
      contentType,
      uploadedAt: Date.now(),
    },
  });

  // Store in database
  const document = await prismaWeb2Client.forumCategoryAttachment.create({
    data: {
      fileName,
      fileSize: buffer.length,
      contentType,
      ipfsCid: result.IpfsHash,
      address,
      dao_slug: slug,
      categoryId,
      isFinancialStatement: contentType === 'application/pdf',
    },
  });

  return {
    success: true,
    data: {
      id: document.id,
      name: document.fileName,
      url: getIPFSUrl(document.ipfsCid),
      ipfsCid: document.ipfsCid,
      createdAt: document.createdAt.toISOString(),
      uploadedBy: address,
      isFinancialStatement: document.isFinancialStatement,
    },
  };
}

Get DUNA Documents

src/lib/actions/forum/attachments.ts
export const getForumCategoryAttachments = async ({
  categoryId,
  archived = false,
}: {
  categoryId: number;
  archived?: boolean;
}) => {
  const now = new Date();

  const attachments = await prismaWeb2Client.forumCategoryAttachment.findMany({
    where: {
      dao_slug: slug,
      categoryId,
      ...(archived
        ? {
            OR: [{ archived: true }, { expirationTime: { lte: now } }],
          }
        : {
            archived: false,
            OR: [
              { revealTime: null, expirationTime: null },
              {
                AND: [
                  { OR: [{ revealTime: null }, { revealTime: { lte: now } }] },
                  { OR: [{ expirationTime: null }, { expirationTime: { gt: now } }] },
                ],
              },
            ],
          }),
    },
  });

  return {
    success: true,
    data: attachments.map((attachment) => ({
      id: attachment.id,
      name: attachment.fileName,
      url: getIPFSUrl(attachment.ipfsCid),
      ipfsCid: attachment.ipfsCid,
      createdAt: attachment.createdAt.toISOString(),
      uploadedBy: attachment.address,
      archived: attachment.archived,
      isFinancialStatement: attachment.isFinancialStatement ?? false,
      revealTime: attachment.revealTime?.toISOString() ?? null,
      expirationTime: attachment.expirationTime?.toISOString() ?? null,
    })),
  };
};

Financial Statements

Financial statements are marked with a special flag:
interface ForumCategoryAttachment {
  id: number;
  fileName: string;
  ipfsCid: string;
  isFinancialStatement: boolean; // Marks document as financial statement
  revealTime: Date | null;       // Scheduled release date
  expirationTime: Date | null;   // Document expiration
}

Timed Document Releases

DUNA documents support scheduled releases:
// Example: Q1 financial statement released on April 1st
const attachment = await prismaWeb2Client.forumCategoryAttachment.create({
  data: {
    fileName: 'Q1-2024-Financial-Statement.pdf',
    ipfsCid: 'QmXxx...',
    categoryId: dunaCategoryId,
    isFinancialStatement: true,
    revealTime: new Date('2024-04-01T00:00:00Z'),
    expirationTime: null, // Never expires
  },
});

Query Logic for Timed Documents

const now = new Date();

// Get visible documents
const visibleDocuments = await prismaWeb2Client.forumCategoryAttachment.findMany({
  where: {
    categoryId: dunaCategoryId,
    archived: false,
    OR: [
      // Not time-restricted
      { revealTime: null, expirationTime: null },
      // Time-restricted but currently visible
      {
        AND: [
          { OR: [{ revealTime: null }, { revealTime: { lte: now } }] },
          { OR: [{ expirationTime: null }, { expirationTime: { gt: now } }] },
        ],
      },
    ],
  },
});

DUNA Administration

Admin-Only Topics

DUNA categories typically restrict topic creation to admins:
const dunaCategory = await prismaWeb2Client.forumCategory.create({
  data: {
    name: 'DUNA Reports',
    description: 'Quarterly reports and official documents',
    dao_slug: slug,
    isDuna: true,
    adminOnlyTopics: true, // Only admins can create topics
  },
});

Permission Checks

src/lib/actions/forum/topics.ts
export async function createForumTopic(data) {
  // Get category info
  const category = await prismaWeb2Client.forumCategory.findUnique({
    where: { id: validatedData.categoryId },
    select: { adminOnlyTopics: true, isDuna: true },
  });

  // Check if admin-only category
  if (category?.adminOnlyTopics) {
    const hasPermission = await checkPermission(
      validatedData.address,
      slug as DaoSlug,
      'forums',
      'topics',
      'create'
    );

    if (!hasPermission) {
      return {
        success: false,
        error: 'Only admins can create topics in this category',
      };
    }
  }

  // Continue with topic creation...
}

UI Components

While the source code doesn’t include DUNA-specific UI components in the provided files, the system supports these component types:

Report Card

interface QuarterlyReportCardProps {
  topic: ForumTopic;
  category: ForumCategory;
  onView: () => void;
}

function QuarterlyReportCard({ topic, category, onView }: QuarterlyReportCardProps) {
  return (
    <div className="report-card">
      <h3>{topic.title}</h3>
      <p>Posted: {new Date(topic.createdAt).toLocaleDateString()}</p>
      <p>Comments: {topic.postsCount - 1}</p>
      
      {category.isDuna && <Badge>DUNA Report</Badge>}
      
      <Button onClick={onView}>View Report</Button>
    </div>
  );
}

Document Upload

function DocumentUploadModal({ categoryId, onSuccess }) {
  const { uploadDocument } = useForum();
  const [uploading, setUploading] = useState(false);
  
  const handleUpload = async (file: File) => {
    setUploading(true);
    
    // Convert to base64
    const reader = new FileReader();
    reader.readAsDataURL(file);
    
    reader.onload = async () => {
      const base64Data = reader.result as string;
      
      const result = await uploadDocument(
        base64Data,
        file.name,
        file.type,
        categoryId
      );
      
      if (result?.success) {
        onSuccess(result.data);
      }
      
      setUploading(false);
    };
  };
  
  return (
    <Modal>
      <FileUploader
        onUpload={handleUpload}
        disabled={uploading}
        accept=".pdf,.doc,.docx"
      />
    </Modal>
  );
}

Data Structure

interface DUNAReport {
  topic: ForumTopic;
  category: ForumCategory;
  documents: ForumCategoryAttachment[];
  comments: ForumPost[];
  metadata: {
    quarter: string;
    year: number;
    publishedDate: string;
    financialStatements: ForumCategoryAttachment[];
    auditReports: ForumCategoryAttachment[];
  };
}

Best Practices

Set adminOnlyTopics: true on DUNA categories to ensure only authorized users can post official reports.
Use the isFinancialStatement flag for proper categorization and compliance tracking.
Use revealTime for scheduled quarterly releases to maintain consistent reporting schedules.
Never hard delete DUNA documents - use archiving instead to maintain compliance records.
Allow community comments on reports for transparency and engagement.

Compliance Considerations

DUNA compliance requires proper documentation and timely reporting. Ensure:
  • Quarterly reports are published on schedule
  • Financial statements are accurate and complete
  • All documents are permanently stored on IPFS
  • Community has opportunity to review and comment
  • Admin actions are logged for audit trail

Next Steps

Attachments

Learn more about IPFS file storage

Moderation

Set up admin controls and permissions

Build docs developers (and LLMs) love