chore: initial project scaffold with admin web, backend, desktop client, and deployment setup

Add monorepo structure for NexaVPN WireGuard control plane including:
- .gitignore for node_modules, build artifacts, and environment files
- README with project overview, monorepo layout, and quick start guide
- Admin web UI with React, Vite, TypeScript, and nginx reverse proxy
- API client with type definitions for users, devices, policies, gateways, and audit logs
- Admin pages for dashboard, users, devices, policies, g
This commit is contained in:
2026-03-15 16:32:34 +01:00
commit 830491cb0d
91 changed files with 5279 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
package audit
import (
"net/http"
"github.com/nexavpn/nexavpn/backend/internal/apiutil"
)
type Handler struct {
service *Service
}
func NewHandler(service *Service) *Handler {
return &Handler{service: service}
}
func (h *Handler) List(w http.ResponseWriter, r *http.Request) {
items, err := h.service.List(r.Context(), 100)
if err != nil {
apiutil.Error(w, http.StatusInternalServerError, "audit_list_failed", "unable to list audit logs")
return
}
apiutil.JSON(w, http.StatusOK, items)
}

View File

@@ -0,0 +1,70 @@
package audit
import (
"context"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgxpool"
)
type PGRepository struct {
db *pgxpool.Pool
}
func NewPGRepository(db *pgxpool.Pool) *PGRepository {
return &PGRepository{db: db}
}
func (r *PGRepository) Write(ctx context.Context, entry Entry) error {
const query = `
insert into audit_logs (id, actor_user_id, entity_type, entity_id, event_type, status, message, metadata)
values ($1, $2, $3, $4, $5, $6, $7, $8::jsonb)
`
_, err := r.db.Exec(
ctx,
query,
uuid.New(),
entry.ActorUserID,
entry.EntityType,
entry.EntityID,
entry.EventType,
entry.Status,
entry.Message,
MarshalMetadata(entry.Metadata),
)
return err
}
func (r *PGRepository) List(ctx context.Context, limit int) ([]map[string]any, error) {
rows, err := r.db.Query(ctx, `
select id, event_type, entity_type, status, message, created_at
from audit_logs
order by created_at desc
limit $1
`, limit)
if err != nil {
return nil, err
}
defer rows.Close()
var entries []map[string]any
for rows.Next() {
var id uuid.UUID
var eventType, entityType, status, message string
var createdAt any
if err := rows.Scan(&id, &eventType, &entityType, &status, &message, &createdAt); err != nil {
return nil, err
}
entries = append(entries, map[string]any{
"id": id,
"event_type": eventType,
"entity_type": entityType,
"status": status,
"message": message,
"created_at": createdAt,
})
}
return entries, rows.Err()
}

View File

@@ -0,0 +1,47 @@
package audit
import (
"context"
"encoding/json"
"github.com/google/uuid"
)
type Entry struct {
ActorUserID *uuid.UUID
EntityType string
EntityID *uuid.UUID
EventType string
Status string
Message string
Metadata map[string]any
}
type Repository interface {
Write(ctx context.Context, entry Entry) error
List(ctx context.Context, limit int) ([]map[string]any, error)
}
type Service struct {
repo Repository
}
func NewService(repo Repository) *Service {
return &Service{repo: repo}
}
func (s *Service) Record(ctx context.Context, entry Entry) error {
if entry.Metadata == nil {
entry.Metadata = map[string]any{}
}
return s.repo.Write(ctx, entry)
}
func (s *Service) List(ctx context.Context, limit int) ([]map[string]any, error) {
return s.repo.List(ctx, limit)
}
func MarshalMetadata(metadata map[string]any) []byte {
raw, _ := json.Marshal(metadata)
return raw
}