Initial Phoenix Sankofa Cloud setup
- Complete project structure with Next.js frontend - GraphQL API backend with Apollo Server - Portal application with NextAuth - Crossplane Proxmox provider - GitOps configurations - CI/CD pipelines - Testing infrastructure (Vitest, Jest, Go tests) - Error handling and monitoring - Security hardening - UI component library - Documentation
This commit is contained in:
132
docs/api/README.md
Normal file
132
docs/api/README.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# API Documentation
|
||||
|
||||
## GraphQL API
|
||||
|
||||
The Phoenix Sankofa Cloud API is a GraphQL API built with Apollo Server.
|
||||
|
||||
### Endpoint
|
||||
|
||||
- Development: `http://localhost:4000/graphql`
|
||||
- Production: `https://api.sankofa.cloud/graphql`
|
||||
|
||||
### Authentication
|
||||
|
||||
All queries and mutations (except `login`) require authentication via JWT token:
|
||||
|
||||
```http
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
### Schema
|
||||
|
||||
See [schema.graphql](./schema.graphql) for the complete GraphQL schema.
|
||||
|
||||
### Queries
|
||||
|
||||
#### Get Resources
|
||||
|
||||
```graphql
|
||||
query GetResources($filter: ResourceFilter) {
|
||||
resources(filter: $filter) {
|
||||
id
|
||||
name
|
||||
type
|
||||
status
|
||||
site {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Get Sites
|
||||
|
||||
```graphql
|
||||
query GetSites {
|
||||
sites {
|
||||
id
|
||||
name
|
||||
region
|
||||
status
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Get Current User
|
||||
|
||||
```graphql
|
||||
query GetMe {
|
||||
me {
|
||||
id
|
||||
email
|
||||
name
|
||||
role
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Mutations
|
||||
|
||||
#### Login
|
||||
|
||||
```graphql
|
||||
mutation Login($email: String!, $password: String!) {
|
||||
login(email: $email, password: $password) {
|
||||
token
|
||||
user {
|
||||
id
|
||||
email
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Create Resource
|
||||
|
||||
```graphql
|
||||
mutation CreateResource($input: CreateResourceInput!) {
|
||||
createResource(input: $input) {
|
||||
id
|
||||
name
|
||||
type
|
||||
status
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
The API returns errors in the standard GraphQL error format:
|
||||
|
||||
```json
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"message": "Authentication required",
|
||||
"extensions": {
|
||||
"code": "UNAUTHENTICATED"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Error Codes
|
||||
|
||||
- `UNAUTHENTICATED`: Authentication required
|
||||
- `FORBIDDEN`: Insufficient permissions
|
||||
- `NOT_FOUND`: Resource not found
|
||||
- `VALIDATION_ERROR`: Input validation failed
|
||||
- `SERVER_ERROR`: Internal server error
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
- 100 requests per minute per IP
|
||||
- 1000 requests per hour per authenticated user
|
||||
|
||||
### Examples
|
||||
|
||||
See [examples.md](./examples.md) for more usage examples.
|
||||
|
||||
109
docs/api/examples.md
Normal file
109
docs/api/examples.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# API Usage Examples
|
||||
|
||||
## Authentication
|
||||
|
||||
### Login
|
||||
|
||||
```javascript
|
||||
const LOGIN_MUTATION = gql`
|
||||
mutation Login($email: String!, $password: String!) {
|
||||
login(email: $email, password: $password) {
|
||||
token
|
||||
user {
|
||||
id
|
||||
email
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const { data } = await client.mutate({
|
||||
mutation: LOGIN_MUTATION,
|
||||
variables: {
|
||||
email: 'user@example.com',
|
||||
password: 'password123'
|
||||
}
|
||||
})
|
||||
|
||||
// Store token
|
||||
localStorage.setItem('token', data.login.token)
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
### Get All Resources
|
||||
|
||||
```javascript
|
||||
const GET_RESOURCES = gql`
|
||||
query GetResources {
|
||||
resources {
|
||||
id
|
||||
name
|
||||
type
|
||||
status
|
||||
site {
|
||||
name
|
||||
region
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const { data } = await client.query({
|
||||
query: GET_RESOURCES
|
||||
})
|
||||
```
|
||||
|
||||
### Create Resource
|
||||
|
||||
```javascript
|
||||
const CREATE_RESOURCE = gql`
|
||||
mutation CreateResource($input: CreateResourceInput!) {
|
||||
createResource(input: $input) {
|
||||
id
|
||||
name
|
||||
type
|
||||
status
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const { data } = await client.mutate({
|
||||
mutation: CREATE_RESOURCE,
|
||||
variables: {
|
||||
input: {
|
||||
name: 'web-server-01',
|
||||
type: 'VM',
|
||||
siteId: 'site-id-here',
|
||||
metadata: {
|
||||
cpu: 4,
|
||||
memory: '8Gi'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Using React Hooks
|
||||
|
||||
```typescript
|
||||
import { useResources, useCreateResource } from '@/lib/graphql/hooks'
|
||||
|
||||
function ResourcesList() {
|
||||
const { data, loading, error } = useResources()
|
||||
const { createResource } = useCreateResource()
|
||||
|
||||
if (loading) return <div>Loading...</div>
|
||||
if (error) return <div>Error: {error.message}</div>
|
||||
|
||||
return (
|
||||
<div>
|
||||
{data?.resources.map(resource => (
|
||||
<div key={resource.id}>{resource.name}</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
134
docs/api/schema.graphql
Normal file
134
docs/api/schema.graphql
Normal file
@@ -0,0 +1,134 @@
|
||||
# GraphQL Schema
|
||||
|
||||
```graphql
|
||||
scalar DateTime
|
||||
scalar JSON
|
||||
|
||||
type Query {
|
||||
health: HealthStatus
|
||||
resources(filter: ResourceFilter): [Resource!]!
|
||||
resource(id: ID!): Resource
|
||||
sites: [Site!]!
|
||||
site(id: ID!): Site
|
||||
me: User
|
||||
users: [User!]!
|
||||
user(id: ID!): User
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
login(email: String!, password: String!): AuthPayload!
|
||||
logout: Boolean!
|
||||
createResource(input: CreateResourceInput!): Resource!
|
||||
updateResource(id: ID!, input: UpdateResourceInput!): Resource!
|
||||
deleteResource(id: ID!): Boolean!
|
||||
createUser(input: CreateUserInput!): User!
|
||||
updateUser(id: ID!, input: UpdateUserInput!): User!
|
||||
deleteUser(id: ID!): Boolean!
|
||||
}
|
||||
|
||||
type Subscription {
|
||||
resourceUpdated(id: ID!): Resource!
|
||||
resourceCreated: Resource!
|
||||
resourceDeleted(id: ID!): ID!
|
||||
}
|
||||
|
||||
type HealthStatus {
|
||||
status: String!
|
||||
timestamp: DateTime!
|
||||
version: String!
|
||||
}
|
||||
|
||||
type Resource {
|
||||
id: ID!
|
||||
name: String!
|
||||
type: ResourceType!
|
||||
status: ResourceStatus!
|
||||
site: Site!
|
||||
metadata: JSON
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime!
|
||||
}
|
||||
|
||||
type Site {
|
||||
id: ID!
|
||||
name: String!
|
||||
region: String!
|
||||
status: SiteStatus!
|
||||
resources: [Resource!]!
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime!
|
||||
}
|
||||
|
||||
type User {
|
||||
id: ID!
|
||||
email: String!
|
||||
name: String!
|
||||
role: UserRole!
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime!
|
||||
}
|
||||
|
||||
type AuthPayload {
|
||||
token: String!
|
||||
user: User!
|
||||
}
|
||||
|
||||
enum ResourceType {
|
||||
VM
|
||||
CONTAINER
|
||||
STORAGE
|
||||
NETWORK
|
||||
}
|
||||
|
||||
enum ResourceStatus {
|
||||
PENDING
|
||||
PROVISIONING
|
||||
RUNNING
|
||||
STOPPED
|
||||
ERROR
|
||||
DELETING
|
||||
}
|
||||
|
||||
enum SiteStatus {
|
||||
ACTIVE
|
||||
INACTIVE
|
||||
MAINTENANCE
|
||||
}
|
||||
|
||||
enum UserRole {
|
||||
ADMIN
|
||||
USER
|
||||
VIEWER
|
||||
}
|
||||
|
||||
input ResourceFilter {
|
||||
type: ResourceType
|
||||
status: ResourceStatus
|
||||
siteId: ID
|
||||
}
|
||||
|
||||
input CreateResourceInput {
|
||||
name: String!
|
||||
type: ResourceType!
|
||||
siteId: ID!
|
||||
metadata: JSON
|
||||
}
|
||||
|
||||
input UpdateResourceInput {
|
||||
name: String
|
||||
metadata: JSON
|
||||
}
|
||||
|
||||
input CreateUserInput {
|
||||
email: String!
|
||||
name: String!
|
||||
password: String!
|
||||
role: UserRole
|
||||
}
|
||||
|
||||
input UpdateUserInput {
|
||||
name: String
|
||||
role: UserRole
|
||||
}
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user