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
+55
View File
@@ -0,0 +1,55 @@
import jwt from 'jsonwebtoken'
import bcrypt from 'bcryptjs'
import { getDb } from '../db'
import { User } from '../types/context'
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key-change-in-production'
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '7d'
export interface AuthPayload {
token: string
user: User
}
export async function login(email: string, password: string): Promise<AuthPayload> {
const db = getDb()
const result = await db.query(
'SELECT id, email, name, password_hash, role, created_at, updated_at FROM users WHERE email = $1',
[email]
)
if (result.rows.length === 0) {
throw new Error('Invalid email or password')
}
const user = result.rows[0]
const isValid = await bcrypt.compare(password, user.password_hash)
if (!isValid) {
throw new Error('Invalid email or password')
}
const token = jwt.sign(
{
id: user.id,
email: user.email,
name: user.name,
role: user.role,
},
JWT_SECRET,
{ expiresIn: JWT_EXPIRES_IN }
)
return {
token,
user: {
id: user.id,
email: user.email,
name: user.name,
role: user.role,
createdAt: user.created_at,
updatedAt: user.updated_at,
},
}
}
+105
View File
@@ -0,0 +1,105 @@
import { Context } from '../types/context'
export async function getResources(context: Context, filter?: any) {
const db = context.db
let query = 'SELECT * FROM resources WHERE 1=1'
const params: any[] = []
let paramCount = 1
if (filter?.type) {
query += ` AND type = $${paramCount}`
params.push(filter.type)
paramCount++
}
if (filter?.status) {
query += ` AND status = $${paramCount}`
params.push(filter.status)
paramCount++
}
if (filter?.siteId) {
query += ` AND site_id = $${paramCount}`
params.push(filter.siteId)
paramCount++
}
query += ' ORDER BY created_at DESC'
const result = await db.query(query, params)
return result.rows.map(mapResource)
}
export async function getResource(context: Context, id: string) {
const db = context.db
const result = await db.query('SELECT * FROM resources WHERE id = $1', [id])
if (result.rows.length === 0) {
throw new Error('Resource not found')
}
return mapResource(result.rows[0])
}
export async function createResource(context: Context, input: any) {
const db = context.db
const result = await db.query(
`INSERT INTO resources (name, type, status, site_id, metadata)
VALUES ($1, $2, $3, $4, $5)
RETURNING *`,
[input.name, input.type, 'PENDING', input.siteId, JSON.stringify(input.metadata || {})]
)
return mapResource(result.rows[0])
}
export async function updateResource(context: Context, id: string, input: any) {
const db = context.db
const updates: string[] = []
const params: any[] = []
let paramCount = 1
if (input.name !== undefined) {
updates.push(`name = $${paramCount}`)
params.push(input.name)
paramCount++
}
if (input.metadata !== undefined) {
updates.push(`metadata = $${paramCount}`)
params.push(JSON.stringify(input.metadata))
paramCount++
}
if (updates.length === 0) {
return getResource(context, id)
}
params.push(id)
const result = await db.query(
`UPDATE resources SET ${updates.join(', ')} WHERE id = $${paramCount} RETURNING *`,
params
)
return mapResource(result.rows[0])
}
export async function deleteResource(context: Context, id: string) {
const db = context.db
await db.query('DELETE FROM resources WHERE id = $1', [id])
return true
}
function mapResource(row: any) {
return {
id: row.id,
name: row.name,
type: row.type,
status: row.status,
siteId: row.site_id,
metadata: row.metadata || {},
createdAt: row.created_at,
updatedAt: row.updated_at,
}
}
+31
View File
@@ -0,0 +1,31 @@
import { Context } from '../types/context'
export async function getSites(context: Context) {
const db = context.db
const result = await db.query('SELECT * FROM sites ORDER BY created_at DESC')
return result.rows.map(mapSite)
}
export async function getSite(context: Context, id: string) {
const db = context.db
const result = await db.query('SELECT * FROM sites WHERE id = $1', [id])
if (result.rows.length === 0) {
throw new Error('Site not found')
}
return mapSite(result.rows[0])
}
function mapSite(row: any) {
return {
id: row.id,
name: row.name,
region: row.region,
status: row.status,
metadata: row.metadata || {},
createdAt: row.created_at,
updatedAt: row.updated_at,
}
}
+88
View File
@@ -0,0 +1,88 @@
import bcrypt from 'bcryptjs'
import { Context } from '../types/context'
export async function getUsers(context: Context) {
const db = context.db
const result = await db.query(
'SELECT id, email, name, role, created_at, updated_at FROM users ORDER BY created_at DESC'
)
return result.rows.map(mapUser)
}
export async function getUser(context: Context, id: string) {
const db = context.db
const result = await db.query(
'SELECT id, email, name, role, created_at, updated_at FROM users WHERE id = $1',
[id]
)
if (result.rows.length === 0) {
throw new Error('User not found')
}
return mapUser(result.rows[0])
}
export async function createUser(context: Context, input: any) {
const db = context.db
const passwordHash = await bcrypt.hash(input.password, 10)
const result = await db.query(
`INSERT INTO users (email, name, password_hash, role)
VALUES ($1, $2, $3, $4)
RETURNING id, email, name, role, created_at, updated_at`,
[input.email, input.name, passwordHash, input.role || 'USER']
)
return mapUser(result.rows[0])
}
export async function updateUser(context: Context, id: string, input: any) {
const db = context.db
const updates: string[] = []
const params: any[] = []
let paramCount = 1
if (input.name !== undefined) {
updates.push(`name = $${paramCount}`)
params.push(input.name)
paramCount++
}
if (input.role !== undefined) {
updates.push(`role = $${paramCount}`)
params.push(input.role)
paramCount++
}
if (updates.length === 0) {
return getUser(context, id)
}
params.push(id)
const result = await db.query(
`UPDATE users SET ${updates.join(', ')} WHERE id = $${paramCount}
RETURNING id, email, name, role, created_at, updated_at`,
params
)
return mapUser(result.rows[0])
}
export async function deleteUser(context: Context, id: string) {
const db = context.db
await db.query('DELETE FROM users WHERE id = $1', [id])
return true
}
function mapUser(row: any) {
return {
id: row.id,
email: row.email,
name: row.name,
role: row.role,
createdAt: row.created_at,
updatedAt: row.updated_at,
}
}