Files
whatshooked/PHASE2_PROGRESS.md
Hein 35a548e7e2
Some checks failed
CI / Test (1.23) (push) Failing after -22m40s
CI / Test (1.22) (push) Failing after -22m36s
CI / Build (push) Failing after -23m32s
CI / Lint (push) Failing after -23m7s
refactor(UI): 🏗️ Ui changes and API changes
2026-02-06 17:03:28 +02:00

17 KiB

Phase 2 Implementation Progress

Completed

1. Tool Documentation (tooldoc/)

Created comprehensive documentation for all libraries and frameworks:

  • CODE_GUIDELINES.md: Complete coding standards, project structure, naming conventions, error handling, testing patterns, and best practices
  • RESOLVESPEC.md: Detailed ResolveSpec integration guide including setup, API patterns, filtering, pagination, and security
  • ORANGURU.md: Oranguru component library guide for enhanced Mantine components
  • REACT_MANTINE_TANSTACK.md: Frontend framework integration guide with project structure and examples

2. Database Storage Layer (pkg/storage/)

Complete database abstraction with GORM support:

Models (storage/models.go):

  • User: Authentication and user management
  • APIKey: API key-based authentication
  • Hook: Webhook registrations with user ownership
  • WhatsAppAccount: WhatsApp account configurations per user
  • EventLog: Audit trail for all operations
  • Session: User session management
  • MessageCache: WhatsApp message caching

Database Management (storage/db.go):

  • PostgreSQL and SQLite support
  • Connection pooling
  • Auto-migration support
  • Health check functionality

Repository Pattern (storage/repository.go):

  • Generic repository with CRUD operations
  • Specialized repositories for each model
  • User-specific queries (by username, email)
  • API key queries (by key, user)
  • Hook and account filtering by user
  • Event log queries with time-based filtering

Seed Data (storage/seed.go):

  • Creates default admin user (username: admin, password: admin123)
  • Safe to run multiple times

3. Authentication Package (pkg/auth/)

Full-featured authentication system:

Core Auth (auth/auth.go):

  • JWT token generation and validation
  • API key authentication
  • Password hashing with bcrypt
  • User login with credential verification
  • API key creation and revocation
  • Permission checking based on roles (admin, user, viewer)

Middleware (auth/middleware.go):

  • AuthMiddleware: Requires authentication (JWT or API key)
  • OptionalAuthMiddleware: Extracts user if present
  • RoleMiddleware: Enforces role-based access control
  • Context helpers for user extraction

4. Web Server Package (pkg/webserver/)

Complete REST API server with authentication:

Server Setup (webserver/server.go):

  • Gorilla Mux router integration
  • Authentication middleware
  • Role-based route protection
  • Public and protected routes
  • Admin-only routes

Core Handlers (webserver/handlers.go):

  • Health check endpoint
  • User login with JWT
  • Get current user profile
  • Change password
  • Create API key
  • Revoke API key
  • List users (admin)
  • Create user (admin)

CRUD Handlers (webserver/handlers_crud.go):

  • Hook management (list, create, get, update, delete)
  • WhatsApp account management (list, create, get, update, delete)
  • API key listing
  • User management (get, update, delete - admin only)
  • Ownership verification for all resources

5. API Endpoints

Complete RESTful API:

Public Endpoints:

  • GET /health - Health check
  • POST /api/v1/auth/login - User login

Authenticated Endpoints:

  • GET /api/v1/users/me - Get current user
  • PUT /api/v1/users/me/password - Change password

API Keys:

  • GET /api/v1/api-keys - List user's API keys
  • POST /api/v1/api-keys - Create API key
  • POST /api/v1/api-keys/{id}/revoke - Revoke API key

Hooks:

  • GET /api/v1/hooks - List user's hooks
  • POST /api/v1/hooks - Create hook
  • GET /api/v1/hooks/{id} - Get hook details
  • PUT /api/v1/hooks/{id} - Update hook
  • DELETE /api/v1/hooks/{id} - Delete hook

WhatsApp Accounts:

  • GET /api/v1/whatsapp-accounts - List user's accounts
  • POST /api/v1/whatsapp-accounts - Create account
  • GET /api/v1/whatsapp-accounts/{id} - Get account details
  • PUT /api/v1/whatsapp-accounts/{id} - Update account
  • DELETE /api/v1/whatsapp-accounts/{id} - Delete account

Admin Endpoints:

  • GET /api/v1/admin/users - List all users
  • POST /api/v1/admin/users - Create user
  • GET /api/v1/admin/users/{id} - Get user
  • PUT /api/v1/admin/users/{id} - Update user
  • DELETE /api/v1/admin/users/{id} - Delete user

6. Dependencies Added

  • github.com/bitechdev/ResolveSpec - REST API framework
  • gorm.io/gorm - ORM
  • gorm.io/driver/postgres - PostgreSQL driver
  • gorm.io/driver/sqlite - SQLite driver
  • github.com/golang-jwt/jwt/v5 - JWT authentication
  • github.com/gorilla/mux - HTTP router
  • golang.org/x/crypto - Bcrypt password hashing
  • All ResolveSpec transitive dependencies

Pending 📋

7-11. Frontend Implementation

The following items require frontend development:

  • React frontend with Mantine and TanStack Start
  • Oranguru integration for grids and forms
  • User login interface
  • API key management UI
  • User-level hooks and WhatsApp account management UI

How to Use

1. Database Setup

import "git.warky.dev/wdevs/whatshooked/pkg/storage"
import "git.warky.dev/wdevs/whatshooked/pkg/config"

// Initialize database
cfg := &config.DatabaseConfig{
    Type:     "postgres", // or "sqlite"
    Host:     "localhost",
    Port:     5432,
    Username: "whatshooked",
    Password: "password",
    Database: "whatshooked",
}

err := storage.Initialize(cfg)
if err != nil {
    log.Fatal(err)
}

// Run migrations
err = storage.AutoMigrate()
if err != nil {
    log.Fatal(err)
}

// Seed default data (creates admin user)
err = storage.SeedData(context.Background())
if err != nil {
    log.Fatal(err)
}

2. Start Web Server

import "git.warky.dev/wdevs/whatshooked/pkg/webserver"

// Create server with JWT secret
server, err := webserver.NewServer("your-secret-key-here")
if err != nil {
    log.Fatal(err)
}

// Start server
log.Fatal(server.Start(":8825"))

3. API Usage Examples

Login:

curl -X POST http://localhost:8825/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"admin123"}'

Create Hook (with JWT):

curl -X POST http://localhost:8825/api/v1/hooks \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My Webhook",
    "url": "https://example.com/webhook",
    "method": "POST",
    "events": ["message.received"],
    "active": true
  }'

Create API Key:

curl -X POST http://localhost:8825/api/v1/api-keys \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"Production API Key"}'

Use API Key:

curl http://localhost:8825/api/v1/hooks \
  -H "Authorization: ApiKey YOUR_API_KEY"

Security Features

  1. Multi-tenancy: Users can only access their own resources (hooks, API keys, accounts)
  2. Role-based Access Control: Admin, user, and viewer roles with hierarchical permissions
  3. JWT Authentication: Secure token-based authentication with 24-hour expiry
  4. API Key Authentication: Alternative authentication for programmatic access
  5. Password Hashing: Bcrypt with default cost for secure password storage
  6. Ownership Verification: All CRUD operations verify resource ownership
  7. Audit Trail: EventLog table for tracking all operations

Next Steps

To complete Phase 2, implement the frontend:

  1. Initialize Frontend Project:

    npm create @tanstack/start@latest
    cd frontend
    npm install @mantine/core @mantine/hooks @mantine/notifications @mantine/form @mantine/datatable
    npm install @warkypublic/oranguru
    npm install @tanstack/react-query axios
    
  2. Create Components:

    • Login page
    • Dashboard layout with navigation
    • User profile page
    • API key management page
    • Hook management page (with Oranguru DataTable)
    • WhatsApp account management page
  3. Integrate with API:

    • Setup axios with JWT token interceptor
    • Create API client modules
    • Implement TanStack Query for data fetching
    • Add error handling and loading states
  4. Build and Deploy:

    • Build frontend: npm run build
    • Serve static files through Go server
    • Configure reverse proxy if needed

Using Oranguru

  1. import { Gridler } from '../Gridler';
  • Gridler is the grid component
  • GlidlerAPIAdaptorForGoLangv2 is used to connect Gridler to RestHeadSpecAPI
const GridExample = () => {
const columns: GridlerColumns = [
    {
      Cell: (row) => {
        const process = `${
          row?.cql2?.length > 0
            ? '🔖'
            : row?.cql1?.length > 0
              ? '📕'
              : row?.status === 1
                ? '💡'
                : row?.status === 2
                  ? '🔒'
                  : '⚙️'
        } ${String(row?.id_process ?? '0')}`;

        return {
          data: process,
          displayData: process,
          status: row?.status,
        } as any;
      },
      id: 'id_process',
      title: 'RID',
      width: 100,
    },

    {
      id: 'process',
      title: 'Process',
      tooltip: (buffer) => {
        return `Process: ${buffer?.process}\nType: ${buffer?.processtype}\nStatus: ${buffer?.status}`;
      },
      width: 200,
    },
    {
      id: 'processtype',
      title: 'Type',
    },

    {
      disableSort: true,
      id: 'status',
      title: 'Status',
      width: 100,
    },
  ];


return (<Gridler
        columns={columns}
        height="100%"
        keyField="id_process"
        onChange={(v) => {
          //console.log('GridlerGoAPIExampleEventlog onChange', v);
          setValues(v);
        }}
        ref={ref}
        scrollToRowKey={selectRow ? parseInt(selectRow, 10) : undefined}
        searchStr={search}
        sections={{ ...sections, rightElementDisabled: false }}
        selectFirstRowOnMount={true}
        selectMode="row"
        title="Go API Example"
        uniqueid="gridtest"
        values={values}
      >
        <GlidlerAPIAdaptorForGoLangv2
          authtoken={apiKey}
          options={[{ type: 'preload', value: 'PRO' }]}
          //options={[{ type: 'fieldfilter', name: 'process', value: 'test' }]}
          url={`${apiUrl}/public/process`}
        />
        <Gridler.FormAdaptor
          changeOnActiveClick={true}
          descriptionField={'process'}
          onRequestForm={(request, data) => {
            console.log('Form requested', request, data);
            //Show form insert,update,delete
          }}
        />
      </Gridler>
      )
}
  1. Former is a form wrapper that uses react-hook-form to handle forms.
import { TextInput } from '@mantine/core';
import { useUncontrolled } from '@mantine/hooks';
import { url } from 'inspector';
import { Controller } from 'react-hook-form';

import { TextInputCtrl, NativeSelectCtrl } from '../../FormerControllers';
import { InlineWrapper } from '../../FormerControllers/Inputs/InlineWrapper';
import NumberInputCtrl from '../../FormerControllers/Inputs/NumberInputCtrl';
import { Former } from '../Former';
import { FormerRestHeadSpecAPI } from '../FormerRestHeadSpecAPI';

export const ApiFormData = (props: {
  onChange?: (values: Record<string, unknown>) => void;
  primeData?: Record<string, unknown>;
  values?: Record<string, unknown>;
}) => {
  const [values, setValues] = useUncontrolled<Record<string, unknown>>({
    defaultValue: { authToken: '', url: '', ...props.primeData },
    finalValue: { authToken: '', url: '', ...props.primeData },
    onChange: props.onChange,
    value: props.values,
  });

  return (
    <Former
      disableHTMlForm
      id="api-form-data"
      layout={{ saveButtonTitle: 'Save URL Parameters' }}
      onAPICall={FormerRestHeadSpecAPI({
        authToken: authToken,
        url: url,
      })}
      onChange={setValues}
      primeData={props.primeData}
      request="update"
      uniqueKeyField="id"
      values={values}
    >
      <TextInputCtrl label="Test" name="test" />
      <NumberInputCtrl label="AgeTest" name="age" />
      <InlineWrapper label="Select One" promptWidth={200}>
        <NativeSelectCtrl data={['One', 'Two', 'Three']} name="option1" />
      </InlineWrapper>
      {/* Controllers can be use but we prefer to use the build TextInputCtrl and such for better integration with Former's state management and validation. However, you can also use the Controller component from react-hook-form to integrate custom inputs like this: */}
      <Controller
        name="url"
        render={({ field }) => <TextInput label="URL" type="url" {...field} />}
      />
      <Controller
        name="authToken"
        render={({ field }) => <TextInput label="Auth Token" type="password" {...field} />}
      />
    </Former>
  );
};
  1. Controls TextInputCtrl,NumberInputCtrl,NativeSelectCtrl and InlineWrapper
  • InlineWrapper is used to display the title on the left and control to the right.
const Renderable = () => {
  return (
    <Former>
      <Stack h="100%" mih="400px" miw="400px" w="100%">
        <TextInputCtrl label="Test" name="test" />
        <NumberInputCtrl label="AgeTest" name="age" />
        <InlineWrapper label="Select One" promptWidth={200}>
            <NativeSelectCtrl data={["One","Two","Three"]} name="option1"/>
        </InlineWrapper>
      </Stack>
    </Former>
  );
};
  1. Boxer is an advanced multi select control with infinite query lookap features for an API.

// Server-Side Example (Simulated)
export const ServerSide: Story = {
  render: () => {
    const [value, setValue] = useState<null | string>(null);

    // Simulate server-side API call
    const handleAPICall = async (params: {
      page: number;
      pageSize: number;
      search?: string;
    }): Promise<{ data: Array<BoxerItem>; total: number }> => {
      // Simulate network delay
      await new Promise((resolve) => setTimeout(resolve, 500));

      // Filter based on search
      let filteredData = [...sampleData];
      if (params.search) {
        filteredData = filteredData.filter((item) =>
          item.label.toLowerCase().includes(params.search!.toLowerCase())
        );
      }

      // Paginate
      const start = params.page * params.pageSize;
      const end = start + params.pageSize;
      const paginatedData = filteredData.slice(start, end);

      return {
        data: paginatedData,
        total: filteredData.length,
      };
    };

    return (
      <div style={{ width: 300 }}>
        <Boxer
          clearable
          dataSource="server"
          label="Favorite Fruit (Server-side)"
          onAPICall={handleAPICall}
          onChange={setValue}
          pageSize={10}
          placeholder="Select a fruit (Server-side)"
          searchable
          value={value}
        />
        <div style={{ marginTop: 20 }}>
          <strong>Selected Value:</strong> {value ?? 'None'}
        </div>
      </div>
    );
  },
};

// Multi-Select Example
export const MultiSelect: Story = {
  render: () => {
    const [value, setValue] = useState<Array<string>>([]);

    return (
      <div style={{ width: 300 }}>
        <Boxer
          clearable
          data={sampleData}
          dataSource="local"
          label="Favorite Fruits"
          multiSelect
          onChange={setValue}
          placeholder="Select fruits"
          searchable
          value={value}
        />
        <div style={{ marginTop: 20 }}>
          <strong>Selected Values:</strong>{' '}
          {value.length > 0 ? value.join(', ') : 'None'}
        </div>
      </div>
    );
  },
};

Database Schema

users
├── id (PK)
├── username (unique)
├── email (unique)
├── password (hashed)
├── full_name
├── role (admin/user/viewer)
├── active
└── timestamps

api_keys
├── id (PK)
├── user_id (FK)
├── name
├── key (hashed)
├── key_prefix
├── last_used_at
├── expires_at
└── timestamps

hooks
├── id (PK)
├── user_id (FK)
├── name
├── url
├── method
├── headers (JSON)
├── events (JSON array)
├── active
├── secret
└── timestamps

whatsapp_accounts
├── id (PK)
├── user_id (FK)
├── account_type
├── phone_number (unique)
├── display_name
├── status
├── config (JSON)
└── timestamps

event_logs
├── id (PK)
├── user_id
├── event_type
├── entity_type
├── entity_id
├── action
├── data (JSON)
└── created_at

sessions
├── id (PK)
├── user_id (FK)
├── token (hashed)
├── expires_at
└── timestamps

Architecture Benefits

  1. Clean Separation: Clear boundaries between storage, auth, and web layers
  2. Testable: Repository pattern and middleware make testing easy
  3. Extensible: Easy to add new resources following the established patterns
  4. Secure: Multi-layered security with authentication, authorization, and ownership
  5. Scalable: Connection pooling and efficient queries
  6. Maintainable: Consistent patterns and comprehensive documentation

Summary

Phase 2 backend is 100% complete with:

  • Comprehensive tool documentation
  • Complete database layer with models and repositories
  • Full authentication system (JWT + API keys)
  • RESTful API with all CRUD operations
  • Role-based access control
  • Multi-tenant architecture
  • Security and audit logging

Only the frontend UI remains to be implemented to complete Phase 2.