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:
defiQUG
2025-11-28 12:54:33 -08:00
commit 6f28146ac3
229 changed files with 43136 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
import { makeExecutableSchema } from '@graphql-tools/schema'
import { typeDefs } from './typeDefs'
import { resolvers } from './resolvers'
export const schema = makeExecutableSchema({
typeDefs,
resolvers,
})
+181
View File
@@ -0,0 +1,181 @@
import { GraphQLError } from 'graphql'
import { Context } from '../types/context'
import * as resourceService from '../services/resource'
import * as siteService from '../services/site'
import * as userService from '../services/user'
import * as authService from '../services/auth'
export const resolvers = {
Query: {
health: () => ({
status: 'ok',
timestamp: new Date(),
version: process.env.npm_package_version || '1.0.0',
}),
resources: async (_: unknown, args: { filter?: any }, context: Context) => {
if (!context.user) {
throw new GraphQLError('Authentication required', {
extensions: { code: 'UNAUTHENTICATED' },
})
}
return resourceService.getResources(context, args.filter)
},
resource: async (_: unknown, args: { id: string }, context: Context) => {
if (!context.user) {
throw new GraphQLError('Authentication required', {
extensions: { code: 'UNAUTHENTICATED' },
})
}
return resourceService.getResource(context, args.id)
},
sites: async (_: unknown, __: unknown, context: Context) => {
if (!context.user) {
throw new GraphQLError('Authentication required', {
extensions: { code: 'UNAUTHENTICATED' },
})
}
return siteService.getSites(context)
},
site: async (_: unknown, args: { id: string }, context: Context) => {
if (!context.user) {
throw new GraphQLError('Authentication required', {
extensions: { code: 'UNAUTHENTICATED' },
})
}
return siteService.getSite(context, args.id)
},
me: async (_: unknown, __: unknown, context: Context) => {
if (!context.user) {
throw new GraphQLError('Authentication required', {
extensions: { code: 'UNAUTHENTICATED' },
})
}
return context.user
},
users: async (_: unknown, __: unknown, context: Context) => {
if (!context.user || context.user.role !== 'ADMIN') {
throw new GraphQLError('Admin access required', {
extensions: { code: 'FORBIDDEN' },
})
}
return userService.getUsers(context)
},
user: async (_: unknown, args: { id: string }, context: Context) => {
if (!context.user || context.user.role !== 'ADMIN') {
throw new GraphQLError('Admin access required', {
extensions: { code: 'FORBIDDEN' },
})
}
return userService.getUser(context, args.id)
},
},
Mutation: {
login: async (_: unknown, args: { email: string; password: string }) => {
return authService.login(args.email, args.password)
},
logout: async (_: unknown, __: unknown, _context: Context) => {
// In a real implementation, invalidate the token
return true
},
createResource: async (
_: unknown,
args: { input: any },
context: Context
) => {
if (!context.user) {
throw new GraphQLError('Authentication required', {
extensions: { code: 'UNAUTHENTICATED' },
})
}
return resourceService.createResource(context, args.input)
},
updateResource: async (
_: unknown,
args: { id: string; input: any },
context: Context
) => {
if (!context.user) {
throw new GraphQLError('Authentication required', {
extensions: { code: 'UNAUTHENTICATED' },
})
}
return resourceService.updateResource(context, args.id, args.input)
},
deleteResource: async (
_: unknown,
args: { id: string },
context: Context
) => {
if (!context.user) {
throw new GraphQLError('Authentication required', {
extensions: { code: 'UNAUTHENTICATED' },
})
}
return resourceService.deleteResource(context, args.id)
},
createUser: async (
_: unknown,
args: { input: any },
context: Context
) => {
if (!context.user || context.user.role !== 'ADMIN') {
throw new GraphQLError('Admin access required', {
extensions: { code: 'FORBIDDEN' },
})
}
return userService.createUser(context, args.input)
},
updateUser: async (
_: unknown,
args: { id: string; input: any },
context: Context
) => {
if (!context.user || context.user.role !== 'ADMIN') {
throw new GraphQLError('Admin access required', {
extensions: { code: 'FORBIDDEN' },
})
}
return userService.updateUser(context, args.id, args.input)
},
deleteUser: async (
_: unknown,
args: { id: string },
context: Context
) => {
if (!context.user || context.user.role !== 'ADMIN') {
throw new GraphQLError('Admin access required', {
extensions: { code: 'FORBIDDEN' },
})
}
return userService.deleteUser(context, args.id)
},
},
Resource: {
site: async (resource: any, __: unknown, context: Context) => {
return siteService.getSite(context, resource.siteId)
},
},
Site: {
resources: async (site: any, __: unknown, context: Context) => {
return resourceService.getResources(context, { siteId: site.id })
},
},
}
+146
View File
@@ -0,0 +1,146 @@
import { gql } from 'graphql-tag'
export const typeDefs = gql`
scalar DateTime
scalar JSON
type Query {
# Health check
health: HealthStatus
# Resources
resources(filter: ResourceFilter): [Resource!]!
resource(id: ID!): Resource
# Sites
sites: [Site!]!
site(id: ID!): Site
# Users
me: User
users: [User!]!
user(id: ID!): User
}
type Mutation {
# Authentication
login(email: String!, password: String!): AuthPayload!
logout: Boolean!
# Resources
createResource(input: CreateResourceInput!): Resource!
updateResource(id: ID!, input: UpdateResourceInput!): Resource!
deleteResource(id: ID!): Boolean!
# Users
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
}
`