648 lines
17 KiB
Markdown
648 lines
17 KiB
Markdown
# 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
|
|
|
|
```go
|
|
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
|
|
|
|
```go
|
|
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**:
|
|
|
|
```bash
|
|
curl -X POST http://localhost:8825/api/v1/auth/login \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"username":"admin","password":"admin123"}'
|
|
```
|
|
|
|
**Create Hook** (with JWT):
|
|
|
|
```bash
|
|
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**:
|
|
|
|
```bash
|
|
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**:
|
|
|
|
```bash
|
|
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**:
|
|
|
|
```bash
|
|
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
|
|
|
|
```ts
|
|
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>
|
|
)
|
|
}
|
|
```
|
|
|
|
2. Former is a form wrapper that uses react-hook-form to handle forms.
|
|
|
|
```ts
|
|
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>
|
|
);
|
|
};
|
|
```
|
|
|
|
3. Controls TextInputCtrl,NumberInputCtrl,NativeSelectCtrl and InlineWrapper
|
|
|
|
- InlineWrapper is used to display the title on the left and control to the right.
|
|
|
|
```ts
|
|
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>
|
|
);
|
|
};
|
|
```
|
|
|
|
4. Boxer is an advanced multi select control with infinite query lookap features for an API.
|
|
|
|
```ts
|
|
|
|
// 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.
|