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:
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user