Skip to content
Open

Test #13

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions apps/api/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "api",
"version": "0.0.1",
"version": "0.0.2",
"description": "",
"author": "",
"private": true,
Expand All @@ -26,7 +26,9 @@
"prisma:reset": "prisma migrate reset --schema=src/prisma/schema.prisma",
"prisma:studio": "prisma studio --schema=src/prisma/schema.prisma",
"prisma:push": "prisma db push --schema=src/prisma/schema.prisma",
"prisma:format": "prisma format --schema=src/prisma/schema.prisma"
"prisma:format": "prisma format --schema=src/prisma/schema.prisma",
"postgres:local:mac": "docker run --name postgres-local -e POSTGRES_PASSWORD=postgres -e POSTGRES_USER=postgres -e POSTGRES_DB=postgres -p 5432:5432 -d postgres:latest",
"postgres:local:windows": "docker run --name postgres-local -e POSTGRES_PASSWORD=postgres -e POSTGRES_USER=postgres -e POSTGRES_DB=postgres -p 5432:5432 -d postgres:latest"
},
"dependencies": {
"@apollo/server": "^4.11.3",
Expand Down
3 changes: 2 additions & 1 deletion apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import { StripeModule } from './stripe/stripe.module';
import { ConfigModule } from '@nestjs/config';
import { SupabaseModule } from './supabase/supabase.module';
import { AuthModule } from './auth/auth.module';
import { GithubModule } from './github/github.module';
@Module({
imports: [ConfigModule.forRoot({ isGlobal: true }), PrismaModule, StripeModule, SupabaseModule, AuthModule],
imports: [ConfigModule.forRoot({ isGlobal: true }), PrismaModule, StripeModule, SupabaseModule, AuthModule, GithubModule],
controllers: [AppController],
providers: [AppService, PrismaService],
})
Expand Down
18 changes: 18 additions & 0 deletions apps/api/src/github/github.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { GithubController } from './webhook.controller';

describe('GithubController', () => {
let controller: GithubController;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [GithubController],
}).compile();

controller = module.get<GithubController>(GithubController);
});

it('should be defined', () => {
expect(controller).toBeDefined();
});
});
9 changes: 9 additions & 0 deletions apps/api/src/github/github.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { GithubWebhookController } from './webhook.controller';
import { GithubService } from './github.service';

@Module({
controllers: [GithubWebhookController],
providers: [GithubService],
})
export class GithubModule {}
18 changes: 18 additions & 0 deletions apps/api/src/github/github.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { GithubService } from './github.service';

describe('GithubService', () => {
let service: GithubService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [GithubService],
}).compile();

service = module.get<GithubService>(GithubService);
});

it('should be defined', () => {
expect(service).toBeDefined();
});
});
4 changes: 4 additions & 0 deletions apps/api/src/github/github.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { Injectable } from '@nestjs/common';

@Injectable()
export class GithubService {}
13 changes: 13 additions & 0 deletions apps/api/src/github/webhook.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Controller, Post, Body, Logger } from '@nestjs/common';

@Controller('github/webhook')
export class GithubWebhookController {
private readonly logger;
constructor() {
this.logger = new Logger(GithubWebhookController.name);
}
@Post()
async webhook(@Body() body: any) {
this.logger.log(body);
}
}
3 changes: 2 additions & 1 deletion apps/web/app/api/github/webhook/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { NextResponse } from 'next/server';
import { headers } from 'next/headers';
import crypto from 'crypto';
import { post } from '@/lib/api';

/**
* Type definition for GitHub webhook events that will be handled
Expand Down Expand Up @@ -153,7 +154,7 @@ export async function POST(request: Request) {

// Process the event
const webhookPayload = JSON.parse(payload) as WebhookPayload;
await handleGitHubEvent(event, webhookPayload);
await post('/github/webhook', webhookPayload);

return NextResponse.json({ status: 'ok' });
} catch (error) {
Expand Down
40 changes: 40 additions & 0 deletions apps/web/lib/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import api from './axios';

// Generic GET request
export const get = async <T>(url: string, params?: Record<string, any>): Promise<T> => {
const response = await api.get<T>(url, { params });
return response.data;
};

// Generic POST request
export const post = async <T>(url: string, data?: any): Promise<T> => {
console.log('post', url, data);
console.log('URL', api.defaults.baseURL + url);
const response = await api.post<T>(url, data);
return response.data;
};

// Generic PUT request
export const put = async <T>(url: string, data?: any): Promise<T> => {
const response = await api.put<T>(url, data);
return response.data;
};

// Generic PATCH request
export const patch = async <T>(url: string, data?: any): Promise<T> => {
const response = await api.patch<T>(url, data);
return response.data;
};

// Generic DELETE request
export const del = async <T>(url: string): Promise<T> => {
const response = await api.delete<T>(url);
return response.data;
};
export default {
get,
post,
put,
patch,
del,
};
102 changes: 102 additions & 0 deletions apps/web/lib/axios.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import axios from 'axios';

/**
* Custom Axios instance configured for API requests
* @module api
*/

/**
* Creates an Axios instance with default configuration
* - Sets base URL from environment variable or localhost
* - Configures JSON content type
* - Sets 10 second timeout
*/
const api = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000/api/v1',
headers: {
'Content-Type': 'application/json',
},
timeout: 10000, // 10 seconds
});

/**
* Request interceptor to handle authentication
* - Checks for auth token in localStorage
* - Adds Bearer token to request headers if found
*/
api.interceptors.request.use(
config => {
// Get token from localStorage (if available)
const token = typeof window !== 'undefined' ? localStorage.getItem('token') : null;

// If token exists, add it to the headers
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}

return config;
},
error => {
return Promise.reject(error);
},
);

/**
* Response interceptor to handle API errors
* - Handles authentication errors (401)
* - Handles authorization errors (403)
* - Handles not found errors (404)
* - Handles server errors (500)
* - Handles network/request setup errors
*/
api.interceptors.response.use(
response => {
return response;
},
error => {
// Handle specific error cases
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
const { status } = error.response;

// Handle 401 Unauthorized errors (token expired or invalid)
if (status === 401) {
// Clear token and redirect to login if in browser
if (typeof window !== 'undefined') {
localStorage.removeItem('token');
// You might want to redirect to login page here
// window.location.href = '/login';
}
}

// Handle 403 Forbidden errors
if (status === 403) {
// Handle forbidden access
console.error('Access forbidden');
}

// Handle 404 Not Found errors
if (status === 404) {
// Handle not found
console.error('Resource not found');
}

// Handle 500 Internal Server errors
if (status === 500) {
// Handle server error
console.error('Server error');
}
} else if (error.request) {
// The request was made but no response was received
console.error('No response received from server');
} else {
// Something happened in setting up the request that triggered an Error
console.error('Error setting up request:', error.message);
}

return Promise.reject(error);
},
);

export default api;
1 change: 1 addition & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@radix-ui/react-toast": "^1.2.6",
"@radix-ui/react-tooltip": "^1.1.8",
"@stripe/stripe-js": "^7.0.0",
"axios": "^1.8.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "saas-boilerplate",
"name": "angry-beard-bot",
"private": true,
"scripts": {
"build": "turbo run build",
Expand Down
Loading
Loading