Deploy to production - ensure all endpoints operational
This commit is contained in:
19
api/deploy-package/DIContainer.d.ts
vendored
Normal file
19
api/deploy-package/DIContainer.d.ts
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
import { CosmosClient, Database, Container } from '@azure/cosmos';
|
||||
import { SecretClient } from '@azure/keyvault-secrets';
|
||||
export interface ServiceContainer {
|
||||
cosmosClient: CosmosClient;
|
||||
database: Database;
|
||||
donationsContainer: Container;
|
||||
volunteersContainer: Container;
|
||||
programsContainer: Container;
|
||||
secretClient: SecretClient;
|
||||
}
|
||||
declare class DIContainer {
|
||||
private static instance;
|
||||
private services;
|
||||
private constructor();
|
||||
static getInstance(): DIContainer;
|
||||
initializeServices(): Promise<ServiceContainer>;
|
||||
getServices(): ServiceContainer;
|
||||
}
|
||||
export default DIContainer;
|
||||
64
api/deploy-package/DIContainer.js
Normal file
64
api/deploy-package/DIContainer.js
Normal file
@@ -0,0 +1,64 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const cosmos_1 = require("@azure/cosmos");
|
||||
const keyvault_secrets_1 = require("@azure/keyvault-secrets");
|
||||
const identity_1 = require("@azure/identity");
|
||||
class DIContainer {
|
||||
static instance;
|
||||
services = null;
|
||||
constructor() { }
|
||||
static getInstance() {
|
||||
if (!DIContainer.instance) {
|
||||
DIContainer.instance = new DIContainer();
|
||||
}
|
||||
return DIContainer.instance;
|
||||
}
|
||||
async initializeServices() {
|
||||
if (this.services) {
|
||||
return this.services;
|
||||
}
|
||||
try {
|
||||
// Initialize Cosmos DB
|
||||
const cosmosConnectionString = process.env.COSMOS_CONNECTION_STRING;
|
||||
if (!cosmosConnectionString) {
|
||||
throw new Error('COSMOS_CONNECTION_STRING is not configured');
|
||||
}
|
||||
const cosmosClient = new cosmos_1.CosmosClient(cosmosConnectionString);
|
||||
const databaseName = process.env.COSMOS_DATABASE_NAME || 'MiraclesInMotion';
|
||||
const database = cosmosClient.database(databaseName);
|
||||
// Get containers
|
||||
const donationsContainer = database.container('donations');
|
||||
const volunteersContainer = database.container('volunteers');
|
||||
const programsContainer = database.container('programs');
|
||||
// Initialize Key Vault
|
||||
const keyVaultUrl = process.env.KEY_VAULT_URL;
|
||||
if (!keyVaultUrl) {
|
||||
throw new Error('KEY_VAULT_URL is not configured');
|
||||
}
|
||||
const credential = new identity_1.DefaultAzureCredential();
|
||||
const secretClient = new keyvault_secrets_1.SecretClient(keyVaultUrl, credential);
|
||||
this.services = {
|
||||
cosmosClient,
|
||||
database,
|
||||
donationsContainer,
|
||||
volunteersContainer,
|
||||
programsContainer,
|
||||
secretClient
|
||||
};
|
||||
console.log('✅ Services initialized successfully');
|
||||
return this.services;
|
||||
}
|
||||
catch (error) {
|
||||
console.error('❌ Failed to initialize services:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
getServices() {
|
||||
if (!this.services) {
|
||||
throw new Error('Services not initialized. Call initializeServices() first.');
|
||||
}
|
||||
return this.services;
|
||||
}
|
||||
}
|
||||
exports.default = DIContainer;
|
||||
//# sourceMappingURL=DIContainer.js.map
|
||||
1
api/deploy-package/DIContainer.js.map
Normal file
1
api/deploy-package/DIContainer.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"DIContainer.js","sourceRoot":"","sources":["../src/DIContainer.ts"],"names":[],"mappings":";;AAAA,0CAAkE;AAClE,8DAAuD;AACvD,8CAAyD;AAWzD,MAAM,WAAW;IACP,MAAM,CAAC,QAAQ,CAAc;IAC7B,QAAQ,GAA4B,IAAI,CAAC;IAEjD,gBAAuB,CAAC;IAEjB,MAAM,CAAC,WAAW;QACvB,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,CAAC;YAC1B,WAAW,CAAC,QAAQ,GAAG,IAAI,WAAW,EAAE,CAAC;QAC3C,CAAC;QACD,OAAO,WAAW,CAAC,QAAQ,CAAC;IAC9B,CAAC;IAEM,KAAK,CAAC,kBAAkB;QAC7B,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,OAAO,IAAI,CAAC,QAAQ,CAAC;QACvB,CAAC;QAED,IAAI,CAAC;YACH,uBAAuB;YACvB,MAAM,sBAAsB,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC;YACpE,IAAI,CAAC,sBAAsB,EAAE,CAAC;gBAC5B,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;YAChE,CAAC;YAED,MAAM,YAAY,GAAG,IAAI,qBAAY,CAAC,sBAAsB,CAAC,CAAC;YAC9D,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,kBAAkB,CAAC;YAC5E,MAAM,QAAQ,GAAG,YAAY,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;YAErD,iBAAiB;YACjB,MAAM,kBAAkB,GAAG,QAAQ,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;YAC3D,MAAM,mBAAmB,GAAG,QAAQ,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;YAC7D,MAAM,iBAAiB,GAAG,QAAQ,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;YAEzD,uBAAuB;YACvB,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;YAC9C,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;YACrD,CAAC;YAED,MAAM,UAAU,GAAG,IAAI,iCAAsB,EAAE,CAAC;YAChD,MAAM,YAAY,GAAG,IAAI,+BAAY,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;YAE/D,IAAI,CAAC,QAAQ,GAAG;gBACd,YAAY;gBACZ,QAAQ;gBACR,kBAAkB;gBAClB,mBAAmB;gBACnB,iBAAiB;gBACjB,YAAY;aACb,CAAC;YAEF,OAAO,CAAC,GAAG,CAAC,qCAAqC,CAAC,CAAC;YACnD,OAAO,IAAI,CAAC,QAAQ,CAAC;QACvB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,kCAAkC,EAAE,KAAK,CAAC,CAAC;YACzD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAEM,WAAW;QAChB,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAC;QAChF,CAAC;QACD,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;CACF;AAED,kBAAe,WAAW,CAAC"}
|
||||
2
api/deploy-package/donations/createDonation.d.ts
vendored
Normal file
2
api/deploy-package/donations/createDonation.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
import { HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions';
|
||||
export declare function createDonation(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit>;
|
||||
128
api/deploy-package/donations/createDonation.js
Normal file
128
api/deploy-package/donations/createDonation.js
Normal file
@@ -0,0 +1,128 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.createDonation = createDonation;
|
||||
const functions_1 = require("@azure/functions");
|
||||
const DIContainer_1 = __importDefault(require("../DIContainer"));
|
||||
const uuid_1 = require("uuid");
|
||||
const stripe_1 = __importDefault(require("stripe"));
|
||||
async function createDonation(request, context) {
|
||||
try {
|
||||
await DIContainer_1.default.getInstance().initializeServices();
|
||||
const { donationsContainer, secretClient } = DIContainer_1.default.getInstance().getServices();
|
||||
// Get request body
|
||||
const donationRequest = await request.json();
|
||||
// Validate required fields
|
||||
if (!donationRequest.amount || !donationRequest.donorEmail || !donationRequest.donorName) {
|
||||
const response = {
|
||||
success: false,
|
||||
error: 'Missing required fields: amount, donorEmail, donorName',
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
return {
|
||||
status: 400,
|
||||
jsonBody: response,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
}
|
||||
};
|
||||
}
|
||||
// Initialize Stripe if payment method is stripe
|
||||
let stripePaymentIntentId;
|
||||
if (donationRequest.paymentMethod === 'stripe') {
|
||||
try {
|
||||
const stripeSecretKey = await secretClient.getSecret('stripe-secret-key');
|
||||
const stripe = new stripe_1.default(stripeSecretKey.value, {
|
||||
apiVersion: '2025-02-24.acacia'
|
||||
});
|
||||
const paymentIntent = await stripe.paymentIntents.create({
|
||||
amount: Math.round(donationRequest.amount * 100), // Convert to cents
|
||||
currency: donationRequest.currency.toLowerCase(),
|
||||
metadata: {
|
||||
donorEmail: donationRequest.donorEmail,
|
||||
donorName: donationRequest.donorName,
|
||||
program: donationRequest.program || 'general'
|
||||
}
|
||||
});
|
||||
stripePaymentIntentId = paymentIntent.id;
|
||||
}
|
||||
catch (stripeError) {
|
||||
context.error('Stripe payment intent creation failed:', stripeError);
|
||||
const response = {
|
||||
success: false,
|
||||
error: 'Payment processing failed',
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
return {
|
||||
status: 500,
|
||||
jsonBody: response,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
// Create donation record
|
||||
const donation = {
|
||||
id: (0, uuid_1.v4)(),
|
||||
amount: donationRequest.amount,
|
||||
currency: donationRequest.currency,
|
||||
donorName: donationRequest.donorName,
|
||||
donorEmail: donationRequest.donorEmail,
|
||||
donorPhone: donationRequest.donorPhone,
|
||||
program: donationRequest.program,
|
||||
isRecurring: donationRequest.isRecurring,
|
||||
frequency: donationRequest.frequency,
|
||||
paymentMethod: donationRequest.paymentMethod,
|
||||
stripePaymentIntentId,
|
||||
status: 'pending',
|
||||
message: donationRequest.message,
|
||||
isAnonymous: donationRequest.isAnonymous,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
};
|
||||
// Save to Cosmos DB
|
||||
await donationsContainer.items.create(donation);
|
||||
const response = {
|
||||
success: true,
|
||||
data: donation,
|
||||
message: 'Donation created successfully',
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
return {
|
||||
status: 201,
|
||||
jsonBody: response,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
}
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
context.error('Error creating donation:', error);
|
||||
const response = {
|
||||
success: false,
|
||||
error: 'Failed to create donation',
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
return {
|
||||
status: 500,
|
||||
jsonBody: response,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
functions_1.app.http('createDonation', {
|
||||
methods: ['POST'],
|
||||
authLevel: 'anonymous',
|
||||
route: 'donations',
|
||||
handler: createDonation
|
||||
});
|
||||
//# sourceMappingURL=createDonation.js.map
|
||||
1
api/deploy-package/donations/createDonation.js.map
Normal file
1
api/deploy-package/donations/createDonation.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"createDonation.js","sourceRoot":"","sources":["../../src/donations/createDonation.ts"],"names":[],"mappings":"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"}
|
||||
2
api/deploy-package/donations/getDonations.d.ts
vendored
Normal file
2
api/deploy-package/donations/getDonations.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
import { HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions';
|
||||
export declare function getDonations(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit>;
|
||||
83
api/deploy-package/donations/getDonations.js
Normal file
83
api/deploy-package/donations/getDonations.js
Normal file
@@ -0,0 +1,83 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getDonations = getDonations;
|
||||
const functions_1 = require("@azure/functions");
|
||||
const DIContainer_1 = __importDefault(require("../DIContainer"));
|
||||
async function getDonations(request, context) {
|
||||
try {
|
||||
await DIContainer_1.default.getInstance().initializeServices();
|
||||
const { donationsContainer } = DIContainer_1.default.getInstance().getServices();
|
||||
const page = parseInt(request.query.get('page') || '1');
|
||||
const limit = parseInt(request.query.get('limit') || '10');
|
||||
const status = request.query.get('status');
|
||||
const program = request.query.get('program');
|
||||
let query = 'SELECT * FROM c WHERE 1=1';
|
||||
const parameters = [];
|
||||
if (status) {
|
||||
query += ' AND c.status = @status';
|
||||
parameters.push({ name: '@status', value: status });
|
||||
}
|
||||
if (program) {
|
||||
query += ' AND c.program = @program';
|
||||
parameters.push({ name: '@program', value: program });
|
||||
}
|
||||
query += ' ORDER BY c.createdAt DESC';
|
||||
const { resources: donations } = await donationsContainer.items
|
||||
.query({
|
||||
query,
|
||||
parameters
|
||||
})
|
||||
.fetchAll();
|
||||
// Simple pagination
|
||||
const total = donations.length;
|
||||
const pages = Math.ceil(total / limit);
|
||||
const startIndex = (page - 1) * limit;
|
||||
const endIndex = startIndex + limit;
|
||||
const paginatedDonations = donations.slice(startIndex, endIndex);
|
||||
const response = {
|
||||
success: true,
|
||||
data: paginatedDonations,
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
pages
|
||||
},
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
return {
|
||||
status: 200,
|
||||
jsonBody: response,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
}
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
context.error('Error fetching donations:', error);
|
||||
const response = {
|
||||
success: false,
|
||||
error: 'Failed to fetch donations',
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
return {
|
||||
status: 500,
|
||||
jsonBody: response,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
functions_1.app.http('getDonations', {
|
||||
methods: ['GET'],
|
||||
authLevel: 'anonymous',
|
||||
route: 'donations',
|
||||
handler: getDonations
|
||||
});
|
||||
//# sourceMappingURL=getDonations.js.map
|
||||
1
api/deploy-package/donations/getDonations.js.map
Normal file
1
api/deploy-package/donations/getDonations.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"getDonations.js","sourceRoot":"","sources":["../../src/donations/getDonations.ts"],"names":[],"mappings":"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"}
|
||||
21
api/deploy-package/host.json
Normal file
21
api/deploy-package/host.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"version": "2.0",
|
||||
"logging": {
|
||||
"applicationInsights": {
|
||||
"samplingSettings": {
|
||||
"isEnabled": true,
|
||||
"excludedTypes": "Request"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extensionBundle": {
|
||||
"id": "Microsoft.Azure.Functions.ExtensionBundle",
|
||||
"version": "[4.*, 5.0.0)"
|
||||
},
|
||||
"functionTimeout": "00:05:00",
|
||||
"languageWorkers": {
|
||||
"node": {
|
||||
"arguments": ["--max-old-space-size=2048"]
|
||||
}
|
||||
}
|
||||
}
|
||||
34
api/deploy-package/package.json
Normal file
34
api/deploy-package/package.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "miracles-in-motion-api",
|
||||
"version": "1.0.0",
|
||||
"description": "Azure Functions API for Miracles in Motion nonprofit platform",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"watch": "tsc -w",
|
||||
"prestart": "npm run build",
|
||||
"start": "func start",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@azure/cosmos": "^4.1.1",
|
||||
"@azure/keyvault-secrets": "^4.8.1",
|
||||
"@azure/identity": "^4.5.0",
|
||||
"@azure/functions": "^4.5.1",
|
||||
"stripe": "^17.3.0",
|
||||
"joi": "^17.13.3",
|
||||
"uuid": "^11.0.3",
|
||||
"cors": "^2.8.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.10.1",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@types/cors": "^2.8.17",
|
||||
"typescript": "^5.6.3",
|
||||
"jest": "^29.7.0",
|
||||
"@types/jest": "^29.5.14"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.0.0"
|
||||
}
|
||||
}
|
||||
174
api/deploy-package/types.d.ts
vendored
Normal file
174
api/deploy-package/types.d.ts
vendored
Normal file
@@ -0,0 +1,174 @@
|
||||
export interface Donation {
|
||||
id: string;
|
||||
amount: number;
|
||||
currency: string;
|
||||
donorName: string;
|
||||
donorEmail: string;
|
||||
donorPhone?: string;
|
||||
program?: string;
|
||||
isRecurring: boolean;
|
||||
frequency?: 'monthly' | 'quarterly' | 'annually';
|
||||
paymentMethod: 'stripe' | 'paypal' | 'bank_transfer';
|
||||
stripePaymentIntentId?: string;
|
||||
status: 'pending' | 'completed' | 'failed' | 'cancelled' | 'refunded';
|
||||
message?: string;
|
||||
isAnonymous: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
export interface Volunteer {
|
||||
id: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
dateOfBirth: string;
|
||||
address: {
|
||||
street: string;
|
||||
city: string;
|
||||
state: string;
|
||||
zipCode: string;
|
||||
country: string;
|
||||
};
|
||||
emergencyContact: {
|
||||
name: string;
|
||||
phone: string;
|
||||
relationship: string;
|
||||
};
|
||||
skills: string[];
|
||||
interests: string[];
|
||||
availability: {
|
||||
monday: boolean;
|
||||
tuesday: boolean;
|
||||
wednesday: boolean;
|
||||
thursday: boolean;
|
||||
friday: boolean;
|
||||
saturday: boolean;
|
||||
sunday: boolean;
|
||||
timeSlots: string[];
|
||||
};
|
||||
experience: string;
|
||||
motivation: string;
|
||||
backgroundCheck: {
|
||||
completed: boolean;
|
||||
completedDate?: string;
|
||||
status?: 'pending' | 'approved' | 'rejected';
|
||||
};
|
||||
status: 'pending' | 'approved' | 'inactive' | 'suspended';
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
lastActivityAt?: string;
|
||||
}
|
||||
export interface Program {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
category: 'education' | 'healthcare' | 'community' | 'environment' | 'arts' | 'other';
|
||||
targetAudience: string;
|
||||
goals: string[];
|
||||
location: {
|
||||
type: 'physical' | 'virtual' | 'hybrid';
|
||||
address?: string;
|
||||
city?: string;
|
||||
state?: string;
|
||||
country?: string;
|
||||
virtualLink?: string;
|
||||
};
|
||||
schedule: {
|
||||
startDate: string;
|
||||
endDate?: string;
|
||||
frequency: 'one-time' | 'weekly' | 'monthly' | 'ongoing';
|
||||
daysOfWeek: string[];
|
||||
timeSlots: string[];
|
||||
};
|
||||
requirements: {
|
||||
minimumAge?: number;
|
||||
maximumAge?: number;
|
||||
skills?: string[];
|
||||
experience?: string;
|
||||
other?: string[];
|
||||
};
|
||||
capacity: {
|
||||
minimum: number;
|
||||
maximum: number;
|
||||
current: number;
|
||||
};
|
||||
budget: {
|
||||
total: number;
|
||||
raised: number;
|
||||
currency: string;
|
||||
};
|
||||
coordinator: {
|
||||
name: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
};
|
||||
volunteers: string[];
|
||||
status: 'planning' | 'active' | 'completed' | 'cancelled' | 'on-hold';
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
images?: string[];
|
||||
documents?: string[];
|
||||
}
|
||||
export interface ApiResponse<T = any> {
|
||||
success: boolean;
|
||||
data?: T;
|
||||
error?: string;
|
||||
message?: string;
|
||||
timestamp: string;
|
||||
}
|
||||
export interface PaginatedResponse<T> extends ApiResponse<T[]> {
|
||||
pagination: {
|
||||
page: number;
|
||||
limit: number;
|
||||
total: number;
|
||||
pages: number;
|
||||
};
|
||||
}
|
||||
export interface CreateDonationRequest {
|
||||
amount: number;
|
||||
currency: string;
|
||||
donorName: string;
|
||||
donorEmail: string;
|
||||
donorPhone?: string;
|
||||
program?: string;
|
||||
isRecurring: boolean;
|
||||
frequency?: 'monthly' | 'quarterly' | 'annually';
|
||||
paymentMethod: 'stripe' | 'paypal' | 'bank_transfer';
|
||||
message?: string;
|
||||
isAnonymous: boolean;
|
||||
}
|
||||
export interface CreateVolunteerRequest {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
dateOfBirth: string;
|
||||
address: {
|
||||
street: string;
|
||||
city: string;
|
||||
state: string;
|
||||
zipCode: string;
|
||||
country: string;
|
||||
};
|
||||
emergencyContact: {
|
||||
name: string;
|
||||
phone: string;
|
||||
relationship: string;
|
||||
};
|
||||
skills: string[];
|
||||
interests: string[];
|
||||
availability: {
|
||||
monday: boolean;
|
||||
tuesday: boolean;
|
||||
wednesday: boolean;
|
||||
thursday: boolean;
|
||||
friday: boolean;
|
||||
saturday: boolean;
|
||||
sunday: boolean;
|
||||
timeSlots: string[];
|
||||
};
|
||||
experience: string;
|
||||
motivation: string;
|
||||
}
|
||||
3
api/deploy-package/types.js
Normal file
3
api/deploy-package/types.js
Normal file
@@ -0,0 +1,3 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
//# sourceMappingURL=types.js.map
|
||||
1
api/deploy-package/types.js.map
Normal file
1
api/deploy-package/types.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
||||
@@ -1,21 +1,21 @@
|
||||
{
|
||||
"version": "2.0",
|
||||
"logging": {
|
||||
"applicationInsights": {
|
||||
"samplingSettings": {
|
||||
"isEnabled": true,
|
||||
"excludedTypes": "Request"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extensionBundle": {
|
||||
"id": "Microsoft.Azure.Functions.ExtensionBundle",
|
||||
"version": "[4.*, 5.0.0)"
|
||||
},
|
||||
"functionTimeout": "00:05:00",
|
||||
"languageWorkers": {
|
||||
"node": {
|
||||
"arguments": ["--max-old-space-size=2048"]
|
||||
}
|
||||
}
|
||||
{
|
||||
"version": "2.0",
|
||||
"logging": {
|
||||
"applicationInsights": {
|
||||
"samplingSettings": {
|
||||
"isEnabled": true,
|
||||
"excludedTypes": "Request"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extensionBundle": {
|
||||
"id": "Microsoft.Azure.Functions.ExtensionBundle",
|
||||
"version": "[4.*, 5.0.0)"
|
||||
},
|
||||
"functionTimeout": "00:05:00",
|
||||
"languageWorkers": {
|
||||
"node": {
|
||||
"arguments": ["--max-old-space-size=2048"]
|
||||
}
|
||||
}
|
||||
}
|
||||
9520
api/package-lock.json
generated
9520
api/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,34 +1,34 @@
|
||||
{
|
||||
"name": "miracles-in-motion-api",
|
||||
"version": "1.0.0",
|
||||
"description": "Azure Functions API for Miracles in Motion nonprofit platform",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"watch": "tsc -w",
|
||||
"prestart": "npm run build",
|
||||
"start": "func start",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@azure/cosmos": "^4.1.1",
|
||||
"@azure/keyvault-secrets": "^4.8.1",
|
||||
"@azure/identity": "^4.5.0",
|
||||
"@azure/functions": "^4.5.1",
|
||||
"stripe": "^17.3.0",
|
||||
"joi": "^17.13.3",
|
||||
"uuid": "^11.0.3",
|
||||
"cors": "^2.8.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.10.1",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@types/cors": "^2.8.17",
|
||||
"typescript": "^5.6.3",
|
||||
"jest": "^29.7.0",
|
||||
"@types/jest": "^29.5.14"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.0.0"
|
||||
}
|
||||
{
|
||||
"name": "miracles-in-motion-api",
|
||||
"version": "1.0.0",
|
||||
"description": "Azure Functions API for Miracles in Motion nonprofit platform",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"watch": "tsc -w",
|
||||
"prestart": "npm run build",
|
||||
"start": "func start",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@azure/cosmos": "^4.1.1",
|
||||
"@azure/keyvault-secrets": "^4.8.1",
|
||||
"@azure/identity": "^4.5.0",
|
||||
"@azure/functions": "^4.5.1",
|
||||
"stripe": "^17.3.0",
|
||||
"joi": "^17.13.3",
|
||||
"uuid": "^11.0.3",
|
||||
"cors": "^2.8.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.10.1",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@types/cors": "^2.8.17",
|
||||
"typescript": "^5.6.3",
|
||||
"jest": "^29.7.0",
|
||||
"@types/jest": "^29.5.14"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,82 +1,82 @@
|
||||
import { CosmosClient, Database, Container } from '@azure/cosmos';
|
||||
import { SecretClient } from '@azure/keyvault-secrets';
|
||||
import { DefaultAzureCredential } from '@azure/identity';
|
||||
|
||||
export interface ServiceContainer {
|
||||
cosmosClient: CosmosClient;
|
||||
database: Database;
|
||||
donationsContainer: Container;
|
||||
volunteersContainer: Container;
|
||||
programsContainer: Container;
|
||||
secretClient: SecretClient;
|
||||
}
|
||||
|
||||
class DIContainer {
|
||||
private static instance: DIContainer;
|
||||
private services: ServiceContainer | null = null;
|
||||
|
||||
private constructor() {}
|
||||
|
||||
public static getInstance(): DIContainer {
|
||||
if (!DIContainer.instance) {
|
||||
DIContainer.instance = new DIContainer();
|
||||
}
|
||||
return DIContainer.instance;
|
||||
}
|
||||
|
||||
public async initializeServices(): Promise<ServiceContainer> {
|
||||
if (this.services) {
|
||||
return this.services;
|
||||
}
|
||||
|
||||
try {
|
||||
// Initialize Cosmos DB
|
||||
const cosmosConnectionString = process.env.COSMOS_CONNECTION_STRING;
|
||||
if (!cosmosConnectionString) {
|
||||
throw new Error('COSMOS_CONNECTION_STRING is not configured');
|
||||
}
|
||||
|
||||
const cosmosClient = new CosmosClient(cosmosConnectionString);
|
||||
const databaseName = process.env.COSMOS_DATABASE_NAME || 'MiraclesInMotion';
|
||||
const database = cosmosClient.database(databaseName);
|
||||
|
||||
// Get containers
|
||||
const donationsContainer = database.container('donations');
|
||||
const volunteersContainer = database.container('volunteers');
|
||||
const programsContainer = database.container('programs');
|
||||
|
||||
// Initialize Key Vault
|
||||
const keyVaultUrl = process.env.KEY_VAULT_URL;
|
||||
if (!keyVaultUrl) {
|
||||
throw new Error('KEY_VAULT_URL is not configured');
|
||||
}
|
||||
|
||||
const credential = new DefaultAzureCredential();
|
||||
const secretClient = new SecretClient(keyVaultUrl, credential);
|
||||
|
||||
this.services = {
|
||||
cosmosClient,
|
||||
database,
|
||||
donationsContainer,
|
||||
volunteersContainer,
|
||||
programsContainer,
|
||||
secretClient
|
||||
};
|
||||
|
||||
console.log('✅ Services initialized successfully');
|
||||
return this.services;
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to initialize services:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public getServices(): ServiceContainer {
|
||||
if (!this.services) {
|
||||
throw new Error('Services not initialized. Call initializeServices() first.');
|
||||
}
|
||||
return this.services;
|
||||
}
|
||||
}
|
||||
|
||||
import { CosmosClient, Database, Container } from '@azure/cosmos';
|
||||
import { SecretClient } from '@azure/keyvault-secrets';
|
||||
import { DefaultAzureCredential } from '@azure/identity';
|
||||
|
||||
export interface ServiceContainer {
|
||||
cosmosClient: CosmosClient;
|
||||
database: Database;
|
||||
donationsContainer: Container;
|
||||
volunteersContainer: Container;
|
||||
programsContainer: Container;
|
||||
secretClient: SecretClient;
|
||||
}
|
||||
|
||||
class DIContainer {
|
||||
private static instance: DIContainer;
|
||||
private services: ServiceContainer | null = null;
|
||||
|
||||
private constructor() {}
|
||||
|
||||
public static getInstance(): DIContainer {
|
||||
if (!DIContainer.instance) {
|
||||
DIContainer.instance = new DIContainer();
|
||||
}
|
||||
return DIContainer.instance;
|
||||
}
|
||||
|
||||
public async initializeServices(): Promise<ServiceContainer> {
|
||||
if (this.services) {
|
||||
return this.services;
|
||||
}
|
||||
|
||||
try {
|
||||
// Initialize Cosmos DB
|
||||
const cosmosConnectionString = process.env.COSMOS_CONNECTION_STRING;
|
||||
if (!cosmosConnectionString) {
|
||||
throw new Error('COSMOS_CONNECTION_STRING is not configured');
|
||||
}
|
||||
|
||||
const cosmosClient = new CosmosClient(cosmosConnectionString);
|
||||
const databaseName = process.env.COSMOS_DATABASE_NAME || 'MiraclesInMotion';
|
||||
const database = cosmosClient.database(databaseName);
|
||||
|
||||
// Get containers
|
||||
const donationsContainer = database.container('donations');
|
||||
const volunteersContainer = database.container('volunteers');
|
||||
const programsContainer = database.container('programs');
|
||||
|
||||
// Initialize Key Vault
|
||||
const keyVaultUrl = process.env.KEY_VAULT_URL;
|
||||
if (!keyVaultUrl) {
|
||||
throw new Error('KEY_VAULT_URL is not configured');
|
||||
}
|
||||
|
||||
const credential = new DefaultAzureCredential();
|
||||
const secretClient = new SecretClient(keyVaultUrl, credential);
|
||||
|
||||
this.services = {
|
||||
cosmosClient,
|
||||
database,
|
||||
donationsContainer,
|
||||
volunteersContainer,
|
||||
programsContainer,
|
||||
secretClient
|
||||
};
|
||||
|
||||
console.log('✅ Services initialized successfully');
|
||||
return this.services;
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to initialize services:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public getServices(): ServiceContainer {
|
||||
if (!this.services) {
|
||||
throw new Error('Services not initialized. Call initializeServices() first.');
|
||||
}
|
||||
return this.services;
|
||||
}
|
||||
}
|
||||
|
||||
export default DIContainer;
|
||||
@@ -1,135 +1,135 @@
|
||||
import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions';
|
||||
import DIContainer from '../DIContainer';
|
||||
import { ApiResponse, CreateDonationRequest, Donation } from '../types';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import Stripe from 'stripe';
|
||||
|
||||
export async function createDonation(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> {
|
||||
try {
|
||||
await DIContainer.getInstance().initializeServices();
|
||||
const { donationsContainer, secretClient } = DIContainer.getInstance().getServices();
|
||||
|
||||
// Get request body
|
||||
const donationRequest = await request.json() as CreateDonationRequest;
|
||||
|
||||
// Validate required fields
|
||||
if (!donationRequest.amount || !donationRequest.donorEmail || !donationRequest.donorName) {
|
||||
const response: ApiResponse = {
|
||||
success: false,
|
||||
error: 'Missing required fields: amount, donorEmail, donorName',
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
return {
|
||||
status: 400,
|
||||
jsonBody: response,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Initialize Stripe if payment method is stripe
|
||||
let stripePaymentIntentId: string | undefined;
|
||||
if (donationRequest.paymentMethod === 'stripe') {
|
||||
try {
|
||||
const stripeSecretKey = await secretClient.getSecret('stripe-secret-key');
|
||||
const stripe = new Stripe(stripeSecretKey.value!, {
|
||||
apiVersion: '2025-02-24.acacia'
|
||||
});
|
||||
|
||||
const paymentIntent = await stripe.paymentIntents.create({
|
||||
amount: Math.round(donationRequest.amount * 100), // Convert to cents
|
||||
currency: donationRequest.currency.toLowerCase(),
|
||||
metadata: {
|
||||
donorEmail: donationRequest.donorEmail,
|
||||
donorName: donationRequest.donorName,
|
||||
program: donationRequest.program || 'general'
|
||||
}
|
||||
});
|
||||
|
||||
stripePaymentIntentId = paymentIntent.id;
|
||||
} catch (stripeError) {
|
||||
context.error('Stripe payment intent creation failed:', stripeError);
|
||||
const response: ApiResponse = {
|
||||
success: false,
|
||||
error: 'Payment processing failed',
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
return {
|
||||
status: 500,
|
||||
jsonBody: response,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Create donation record
|
||||
const donation: Donation = {
|
||||
id: uuidv4(),
|
||||
amount: donationRequest.amount,
|
||||
currency: donationRequest.currency,
|
||||
donorName: donationRequest.donorName,
|
||||
donorEmail: donationRequest.donorEmail,
|
||||
donorPhone: donationRequest.donorPhone,
|
||||
program: donationRequest.program,
|
||||
isRecurring: donationRequest.isRecurring,
|
||||
frequency: donationRequest.frequency,
|
||||
paymentMethod: donationRequest.paymentMethod,
|
||||
stripePaymentIntentId,
|
||||
status: 'pending',
|
||||
message: donationRequest.message,
|
||||
isAnonymous: donationRequest.isAnonymous,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
// Save to Cosmos DB
|
||||
await donationsContainer.items.create(donation);
|
||||
|
||||
const response: ApiResponse<Donation> = {
|
||||
success: true,
|
||||
data: donation,
|
||||
message: 'Donation created successfully',
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
return {
|
||||
status: 201,
|
||||
jsonBody: response,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
context.error('Error creating donation:', error);
|
||||
|
||||
const response: ApiResponse = {
|
||||
success: false,
|
||||
error: 'Failed to create donation',
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
return {
|
||||
status: 500,
|
||||
jsonBody: response,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
app.http('createDonation', {
|
||||
methods: ['POST'],
|
||||
authLevel: 'anonymous',
|
||||
route: 'donations',
|
||||
handler: createDonation
|
||||
import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions';
|
||||
import DIContainer from '../DIContainer';
|
||||
import { ApiResponse, CreateDonationRequest, Donation } from '../types';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import Stripe from 'stripe';
|
||||
|
||||
export async function createDonation(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> {
|
||||
try {
|
||||
await DIContainer.getInstance().initializeServices();
|
||||
const { donationsContainer, secretClient } = DIContainer.getInstance().getServices();
|
||||
|
||||
// Get request body
|
||||
const donationRequest = await request.json() as CreateDonationRequest;
|
||||
|
||||
// Validate required fields
|
||||
if (!donationRequest.amount || !donationRequest.donorEmail || !donationRequest.donorName) {
|
||||
const response: ApiResponse = {
|
||||
success: false,
|
||||
error: 'Missing required fields: amount, donorEmail, donorName',
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
return {
|
||||
status: 400,
|
||||
jsonBody: response,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Initialize Stripe if payment method is stripe
|
||||
let stripePaymentIntentId: string | undefined;
|
||||
if (donationRequest.paymentMethod === 'stripe') {
|
||||
try {
|
||||
const stripeSecretKey = await secretClient.getSecret('stripe-secret-key');
|
||||
const stripe = new Stripe(stripeSecretKey.value!, {
|
||||
apiVersion: '2025-02-24.acacia'
|
||||
});
|
||||
|
||||
const paymentIntent = await stripe.paymentIntents.create({
|
||||
amount: Math.round(donationRequest.amount * 100), // Convert to cents
|
||||
currency: donationRequest.currency.toLowerCase(),
|
||||
metadata: {
|
||||
donorEmail: donationRequest.donorEmail,
|
||||
donorName: donationRequest.donorName,
|
||||
program: donationRequest.program || 'general'
|
||||
}
|
||||
});
|
||||
|
||||
stripePaymentIntentId = paymentIntent.id;
|
||||
} catch (stripeError) {
|
||||
context.error('Stripe payment intent creation failed:', stripeError);
|
||||
const response: ApiResponse = {
|
||||
success: false,
|
||||
error: 'Payment processing failed',
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
return {
|
||||
status: 500,
|
||||
jsonBody: response,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Create donation record
|
||||
const donation: Donation = {
|
||||
id: uuidv4(),
|
||||
amount: donationRequest.amount,
|
||||
currency: donationRequest.currency,
|
||||
donorName: donationRequest.donorName,
|
||||
donorEmail: donationRequest.donorEmail,
|
||||
donorPhone: donationRequest.donorPhone,
|
||||
program: donationRequest.program,
|
||||
isRecurring: donationRequest.isRecurring,
|
||||
frequency: donationRequest.frequency,
|
||||
paymentMethod: donationRequest.paymentMethod,
|
||||
stripePaymentIntentId,
|
||||
status: 'pending',
|
||||
message: donationRequest.message,
|
||||
isAnonymous: donationRequest.isAnonymous,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
// Save to Cosmos DB
|
||||
await donationsContainer.items.create(donation);
|
||||
|
||||
const response: ApiResponse<Donation> = {
|
||||
success: true,
|
||||
data: donation,
|
||||
message: 'Donation created successfully',
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
return {
|
||||
status: 201,
|
||||
jsonBody: response,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
context.error('Error creating donation:', error);
|
||||
|
||||
const response: ApiResponse = {
|
||||
success: false,
|
||||
error: 'Failed to create donation',
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
return {
|
||||
status: 500,
|
||||
jsonBody: response,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
app.http('createDonation', {
|
||||
methods: ['POST'],
|
||||
authLevel: 'anonymous',
|
||||
route: 'donations',
|
||||
handler: createDonation
|
||||
});
|
||||
@@ -1,90 +1,90 @@
|
||||
import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions';
|
||||
import DIContainer from '../DIContainer';
|
||||
import { ApiResponse, PaginatedResponse, Donation } from '../types';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export async function getDonations(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> {
|
||||
try {
|
||||
await DIContainer.getInstance().initializeServices();
|
||||
const { donationsContainer } = DIContainer.getInstance().getServices();
|
||||
|
||||
const page = parseInt(request.query.get('page') || '1');
|
||||
const limit = parseInt(request.query.get('limit') || '10');
|
||||
const status = request.query.get('status');
|
||||
const program = request.query.get('program');
|
||||
|
||||
let query = 'SELECT * FROM c WHERE 1=1';
|
||||
const parameters: any[] = [];
|
||||
|
||||
if (status) {
|
||||
query += ' AND c.status = @status';
|
||||
parameters.push({ name: '@status', value: status });
|
||||
}
|
||||
|
||||
if (program) {
|
||||
query += ' AND c.program = @program';
|
||||
parameters.push({ name: '@program', value: program });
|
||||
}
|
||||
|
||||
query += ' ORDER BY c.createdAt DESC';
|
||||
|
||||
const { resources: donations } = await donationsContainer.items
|
||||
.query({
|
||||
query,
|
||||
parameters
|
||||
})
|
||||
.fetchAll();
|
||||
|
||||
// Simple pagination
|
||||
const total = donations.length;
|
||||
const pages = Math.ceil(total / limit);
|
||||
const startIndex = (page - 1) * limit;
|
||||
const endIndex = startIndex + limit;
|
||||
const paginatedDonations = donations.slice(startIndex, endIndex);
|
||||
|
||||
const response: PaginatedResponse<Donation> = {
|
||||
success: true,
|
||||
data: paginatedDonations,
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
pages
|
||||
},
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
jsonBody: response,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
context.error('Error fetching donations:', error);
|
||||
|
||||
const response: ApiResponse = {
|
||||
success: false,
|
||||
error: 'Failed to fetch donations',
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
return {
|
||||
status: 500,
|
||||
jsonBody: response,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
app.http('getDonations', {
|
||||
methods: ['GET'],
|
||||
authLevel: 'anonymous',
|
||||
route: 'donations',
|
||||
handler: getDonations
|
||||
import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions';
|
||||
import DIContainer from '../DIContainer';
|
||||
import { ApiResponse, PaginatedResponse, Donation } from '../types';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export async function getDonations(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> {
|
||||
try {
|
||||
await DIContainer.getInstance().initializeServices();
|
||||
const { donationsContainer } = DIContainer.getInstance().getServices();
|
||||
|
||||
const page = parseInt(request.query.get('page') || '1');
|
||||
const limit = parseInt(request.query.get('limit') || '10');
|
||||
const status = request.query.get('status');
|
||||
const program = request.query.get('program');
|
||||
|
||||
let query = 'SELECT * FROM c WHERE 1=1';
|
||||
const parameters: any[] = [];
|
||||
|
||||
if (status) {
|
||||
query += ' AND c.status = @status';
|
||||
parameters.push({ name: '@status', value: status });
|
||||
}
|
||||
|
||||
if (program) {
|
||||
query += ' AND c.program = @program';
|
||||
parameters.push({ name: '@program', value: program });
|
||||
}
|
||||
|
||||
query += ' ORDER BY c.createdAt DESC';
|
||||
|
||||
const { resources: donations } = await donationsContainer.items
|
||||
.query({
|
||||
query,
|
||||
parameters
|
||||
})
|
||||
.fetchAll();
|
||||
|
||||
// Simple pagination
|
||||
const total = donations.length;
|
||||
const pages = Math.ceil(total / limit);
|
||||
const startIndex = (page - 1) * limit;
|
||||
const endIndex = startIndex + limit;
|
||||
const paginatedDonations = donations.slice(startIndex, endIndex);
|
||||
|
||||
const response: PaginatedResponse<Donation> = {
|
||||
success: true,
|
||||
data: paginatedDonations,
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
pages
|
||||
},
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
jsonBody: response,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
context.error('Error fetching donations:', error);
|
||||
|
||||
const response: ApiResponse = {
|
||||
success: false,
|
||||
error: 'Failed to fetch donations',
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
return {
|
||||
status: 500,
|
||||
jsonBody: response,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
app.http('getDonations', {
|
||||
methods: ['GET'],
|
||||
authLevel: 'anonymous',
|
||||
route: 'donations',
|
||||
handler: getDonations
|
||||
});
|
||||
358
api/src/types.ts
358
api/src/types.ts
@@ -1,180 +1,180 @@
|
||||
export interface Donation {
|
||||
id: string;
|
||||
amount: number;
|
||||
currency: string;
|
||||
donorName: string;
|
||||
donorEmail: string;
|
||||
donorPhone?: string;
|
||||
program?: string;
|
||||
isRecurring: boolean;
|
||||
frequency?: 'monthly' | 'quarterly' | 'annually';
|
||||
paymentMethod: 'stripe' | 'paypal' | 'bank_transfer';
|
||||
stripePaymentIntentId?: string;
|
||||
status: 'pending' | 'completed' | 'failed' | 'cancelled' | 'refunded';
|
||||
message?: string;
|
||||
isAnonymous: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface Volunteer {
|
||||
id: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
dateOfBirth: string;
|
||||
address: {
|
||||
street: string;
|
||||
city: string;
|
||||
state: string;
|
||||
zipCode: string;
|
||||
country: string;
|
||||
};
|
||||
emergencyContact: {
|
||||
name: string;
|
||||
phone: string;
|
||||
relationship: string;
|
||||
};
|
||||
skills: string[];
|
||||
interests: string[];
|
||||
availability: {
|
||||
monday: boolean;
|
||||
tuesday: boolean;
|
||||
wednesday: boolean;
|
||||
thursday: boolean;
|
||||
friday: boolean;
|
||||
saturday: boolean;
|
||||
sunday: boolean;
|
||||
timeSlots: string[];
|
||||
};
|
||||
experience: string;
|
||||
motivation: string;
|
||||
backgroundCheck: {
|
||||
completed: boolean;
|
||||
completedDate?: string;
|
||||
status?: 'pending' | 'approved' | 'rejected';
|
||||
};
|
||||
status: 'pending' | 'approved' | 'inactive' | 'suspended';
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
lastActivityAt?: string;
|
||||
}
|
||||
|
||||
export interface Program {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
category: 'education' | 'healthcare' | 'community' | 'environment' | 'arts' | 'other';
|
||||
targetAudience: string;
|
||||
goals: string[];
|
||||
location: {
|
||||
type: 'physical' | 'virtual' | 'hybrid';
|
||||
address?: string;
|
||||
city?: string;
|
||||
state?: string;
|
||||
country?: string;
|
||||
virtualLink?: string;
|
||||
};
|
||||
schedule: {
|
||||
startDate: string;
|
||||
endDate?: string;
|
||||
frequency: 'one-time' | 'weekly' | 'monthly' | 'ongoing';
|
||||
daysOfWeek: string[];
|
||||
timeSlots: string[];
|
||||
};
|
||||
requirements: {
|
||||
minimumAge?: number;
|
||||
maximumAge?: number;
|
||||
skills?: string[];
|
||||
experience?: string;
|
||||
other?: string[];
|
||||
};
|
||||
capacity: {
|
||||
minimum: number;
|
||||
maximum: number;
|
||||
current: number;
|
||||
};
|
||||
budget: {
|
||||
total: number;
|
||||
raised: number;
|
||||
currency: string;
|
||||
};
|
||||
coordinator: {
|
||||
name: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
};
|
||||
volunteers: string[]; // Array of volunteer IDs
|
||||
status: 'planning' | 'active' | 'completed' | 'cancelled' | 'on-hold';
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
images?: string[];
|
||||
documents?: string[];
|
||||
}
|
||||
|
||||
export interface ApiResponse<T = any> {
|
||||
success: boolean;
|
||||
data?: T;
|
||||
error?: string;
|
||||
message?: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export interface PaginatedResponse<T> extends ApiResponse<T[]> {
|
||||
pagination: {
|
||||
page: number;
|
||||
limit: number;
|
||||
total: number;
|
||||
pages: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface CreateDonationRequest {
|
||||
amount: number;
|
||||
currency: string;
|
||||
donorName: string;
|
||||
donorEmail: string;
|
||||
donorPhone?: string;
|
||||
program?: string;
|
||||
isRecurring: boolean;
|
||||
frequency?: 'monthly' | 'quarterly' | 'annually';
|
||||
paymentMethod: 'stripe' | 'paypal' | 'bank_transfer';
|
||||
message?: string;
|
||||
isAnonymous: boolean;
|
||||
}
|
||||
|
||||
export interface CreateVolunteerRequest {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
dateOfBirth: string;
|
||||
address: {
|
||||
street: string;
|
||||
city: string;
|
||||
state: string;
|
||||
zipCode: string;
|
||||
country: string;
|
||||
};
|
||||
emergencyContact: {
|
||||
name: string;
|
||||
phone: string;
|
||||
relationship: string;
|
||||
};
|
||||
skills: string[];
|
||||
interests: string[];
|
||||
availability: {
|
||||
monday: boolean;
|
||||
tuesday: boolean;
|
||||
wednesday: boolean;
|
||||
thursday: boolean;
|
||||
friday: boolean;
|
||||
saturday: boolean;
|
||||
sunday: boolean;
|
||||
timeSlots: string[];
|
||||
};
|
||||
experience: string;
|
||||
motivation: string;
|
||||
export interface Donation {
|
||||
id: string;
|
||||
amount: number;
|
||||
currency: string;
|
||||
donorName: string;
|
||||
donorEmail: string;
|
||||
donorPhone?: string;
|
||||
program?: string;
|
||||
isRecurring: boolean;
|
||||
frequency?: 'monthly' | 'quarterly' | 'annually';
|
||||
paymentMethod: 'stripe' | 'paypal' | 'bank_transfer';
|
||||
stripePaymentIntentId?: string;
|
||||
status: 'pending' | 'completed' | 'failed' | 'cancelled' | 'refunded';
|
||||
message?: string;
|
||||
isAnonymous: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface Volunteer {
|
||||
id: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
dateOfBirth: string;
|
||||
address: {
|
||||
street: string;
|
||||
city: string;
|
||||
state: string;
|
||||
zipCode: string;
|
||||
country: string;
|
||||
};
|
||||
emergencyContact: {
|
||||
name: string;
|
||||
phone: string;
|
||||
relationship: string;
|
||||
};
|
||||
skills: string[];
|
||||
interests: string[];
|
||||
availability: {
|
||||
monday: boolean;
|
||||
tuesday: boolean;
|
||||
wednesday: boolean;
|
||||
thursday: boolean;
|
||||
friday: boolean;
|
||||
saturday: boolean;
|
||||
sunday: boolean;
|
||||
timeSlots: string[];
|
||||
};
|
||||
experience: string;
|
||||
motivation: string;
|
||||
backgroundCheck: {
|
||||
completed: boolean;
|
||||
completedDate?: string;
|
||||
status?: 'pending' | 'approved' | 'rejected';
|
||||
};
|
||||
status: 'pending' | 'approved' | 'inactive' | 'suspended';
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
lastActivityAt?: string;
|
||||
}
|
||||
|
||||
export interface Program {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
category: 'education' | 'healthcare' | 'community' | 'environment' | 'arts' | 'other';
|
||||
targetAudience: string;
|
||||
goals: string[];
|
||||
location: {
|
||||
type: 'physical' | 'virtual' | 'hybrid';
|
||||
address?: string;
|
||||
city?: string;
|
||||
state?: string;
|
||||
country?: string;
|
||||
virtualLink?: string;
|
||||
};
|
||||
schedule: {
|
||||
startDate: string;
|
||||
endDate?: string;
|
||||
frequency: 'one-time' | 'weekly' | 'monthly' | 'ongoing';
|
||||
daysOfWeek: string[];
|
||||
timeSlots: string[];
|
||||
};
|
||||
requirements: {
|
||||
minimumAge?: number;
|
||||
maximumAge?: number;
|
||||
skills?: string[];
|
||||
experience?: string;
|
||||
other?: string[];
|
||||
};
|
||||
capacity: {
|
||||
minimum: number;
|
||||
maximum: number;
|
||||
current: number;
|
||||
};
|
||||
budget: {
|
||||
total: number;
|
||||
raised: number;
|
||||
currency: string;
|
||||
};
|
||||
coordinator: {
|
||||
name: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
};
|
||||
volunteers: string[]; // Array of volunteer IDs
|
||||
status: 'planning' | 'active' | 'completed' | 'cancelled' | 'on-hold';
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
images?: string[];
|
||||
documents?: string[];
|
||||
}
|
||||
|
||||
export interface ApiResponse<T = any> {
|
||||
success: boolean;
|
||||
data?: T;
|
||||
error?: string;
|
||||
message?: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export interface PaginatedResponse<T> extends ApiResponse<T[]> {
|
||||
pagination: {
|
||||
page: number;
|
||||
limit: number;
|
||||
total: number;
|
||||
pages: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface CreateDonationRequest {
|
||||
amount: number;
|
||||
currency: string;
|
||||
donorName: string;
|
||||
donorEmail: string;
|
||||
donorPhone?: string;
|
||||
program?: string;
|
||||
isRecurring: boolean;
|
||||
frequency?: 'monthly' | 'quarterly' | 'annually';
|
||||
paymentMethod: 'stripe' | 'paypal' | 'bank_transfer';
|
||||
message?: string;
|
||||
isAnonymous: boolean;
|
||||
}
|
||||
|
||||
export interface CreateVolunteerRequest {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
dateOfBirth: string;
|
||||
address: {
|
||||
street: string;
|
||||
city: string;
|
||||
state: string;
|
||||
zipCode: string;
|
||||
country: string;
|
||||
};
|
||||
emergencyContact: {
|
||||
name: string;
|
||||
phone: string;
|
||||
relationship: string;
|
||||
};
|
||||
skills: string[];
|
||||
interests: string[];
|
||||
availability: {
|
||||
monday: boolean;
|
||||
tuesday: boolean;
|
||||
wednesday: boolean;
|
||||
thursday: boolean;
|
||||
friday: boolean;
|
||||
saturday: boolean;
|
||||
sunday: boolean;
|
||||
timeSlots: string[];
|
||||
};
|
||||
experience: string;
|
||||
motivation: string;
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "commonjs",
|
||||
"lib": ["ES2022"],
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "**/*.test.ts", "dist"]
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "commonjs",
|
||||
"lib": ["ES2022"],
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "**/*.test.ts", "dist"]
|
||||
}
|
||||
Reference in New Issue
Block a user