Skip to content
Merged
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
185 changes: 166 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,183 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
# Cardano API Projects Directory

A list of Cardano API Projects

## Tech Stack

### Frontend

- Next.js with TypeScript
- Tailwind CSS for styling
- Jest for testing

### Backend

- Rust
- Axum for REST API
- Postgres for database
- SQLx for database operations
- Docker for containerization

## Prerequisites

- Node.js (v18 or higher)
- Rust (latest stable version)
- Docker and Docker Compose
- PostgreSQL (if running without Docker)

## Getting Started

First, run the development server:
### Clone the repository

```bash
git clone https://github.com/cardanoapi/cardanoapi.io.git
cd cardanoapi.io
```

### Backend Development

1. Navigate to the backend directory:

```bash
cd backend/api

```

2. Setup .env file as:

```
//backend/api/.env
POSTGRES_HOST=127.0.0.1
POSTGRES_PORT=6500
POSTGRES_USER= <YOUR_POSTGRES_USERNAME>
POSTGRES_PASSWORD=<YOUR_POSTGRES_PASSWORD>
POSTGRES_DB=rust_sqlx

DATABASE_URL=postgresql://<YOUR_POSTGRES_USERNAME>:<YOUR_POSTGRES_PASSWORD>@localhost:6500/rust_sqlx?schema=public

PGADMIN_DEFAULT_EMAIL=admin@admin.com
PGADMIN_DEFAULT_PASSWORD=<YOUR_PGADMIN_PASSWORD>

```

3. Install dependencies:

```bash
cargo build
```

4. Start the Postgres Database and PG Admin

```bash
docker compose -f docker-compose.yml up
```

5. Run the Rust server:

```bash
cargo run
```

Test if the server is working at `http://localhost:8000/test`

### Frontend Development

1. Install dependencies:

```bash
npm install
```

2. Setup .env file as:

```
//.env
POSTGRES_HOST=127.0.0.1
POSTGRES_PORT=6500
POSTGRES_USER= <YOUR_POSTGRES_USERNAME>
POSTGRES_PASSWORD=<YOUR_POSTGRES_PASSWORD>
POSTGRES_DB=rust_sqlx

DATABASE_URL=postgresql://<YOUR_POSTGRES_USERNAME>:<YOUR_POSTGRES_PASSWORD>@localhost:6500/rust_sqlx

PGADMIN_DEFAULT_EMAIL=admin@admin.com
PGADMIN_DEFAULT_PASSWORD=<YOUR_PGADMIN_PASSWORD>


# Frontend Configuration
API_URL=http://localhost:8000
NODE_ENV=development
```

2. Run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
The frontend will be available at `http://localhost:3000`

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
### Using Docker

This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
#### Development Environment

## Learn More
```bash
docker compose -f docker-compose-dev.yml up
```

To learn more about Next.js, take a look at the following resources:
#### Production Environment

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
```bash
docker compose -f docker-compose-prod.yml up
```

## Database Migrations

The project uses SQL migrations located in `backend/api/migrations/`. To run migrations:

1. Ensure you're in the backend/api directory
2. Run:

```bash
sqlx migrate run
```

## Testing

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
### Frontend Tests

## Deploy on Vercel
```bash
npm test
```

## Project Structure

```

├── src/
│ └── app/
│ ├── globals.css
│ ├── layout.tsx
|__ projects
|_[id]
|__page.tsx //Project Detail Page
│ ├── page.tsx //Home Page (/)
│ ├── Component/
│ │ ├── Card.tsx
│ │ ├── Pagination.tsx
│ │ └── SimilarProjects.tsx

└── public/ # Static assets
└── backend (Rust)
└── api/
├── migrations/ # Database migrations
└── src/ # Rust source code
```

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
## Contributing

Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
113 changes: 99 additions & 14 deletions __tests__/Project.test.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
/* eslint-disable testing-library/no-node-access */
/* eslint-disable testing-library/no-container */
/* eslint-disable jsx-a11y/alt-text */
/* eslint-disable @next/next/no-img-element */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { render, screen } from "@testing-library/react";
import ProjectPage from "@/app/projects/[id]/page";
import ProjectPage, { generateMetadata } from "../src/app/projects/[id]/page";

import "@testing-library/jest-dom";

// Mock next/image
Expand Down Expand Up @@ -37,9 +40,6 @@ jest.mock("../src/app/Component/SimilarProjects", () => {
};
});

// Mock fetch function
global.fetch = jest.fn();

// Mock project data
const mockProject = {
id: "1",
Expand All @@ -60,10 +60,14 @@ const mockApiResponse = {
status: "success",
};

// Properly set up the fetch mock
const mockFetch = jest.fn();
global.fetch = mockFetch;

describe("ProjectPage", () => {
beforeEach(() => {
jest.clearAllMocks();
(global.fetch as jest.Mock).mockResolvedValue({
mockFetch.mockResolvedValue({
ok: true,
json: async () => mockApiResponse,
});
Expand All @@ -73,21 +77,102 @@ describe("ProjectPage", () => {
const params = Promise.resolve({ id: "1" });
render(await ProjectPage({ params }));

// Check if main project details are rendered
expect(screen.getByText("Test Project")).toBeInTheDocument();
expect(screen.getByText("Test Description")).toBeInTheDocument();
expect(screen.getByText("Test About Section")).toBeInTheDocument();

// Check if navigation elements are present
expect(screen.getByText("Projects")).toBeInTheDocument();
expect(screen.getAllByText("Visit").length).toBeGreaterThan(0); // Ensures buttons exist
expect(screen.getAllByText("Visit").length).toBe(2); // One for mobile, one for desktop
expect(screen.getByTestId("similar-projects")).toBeInTheDocument();
});

it("handles missing project ID", async () => {
const params = Promise.resolve({ id: "" });
const { container } = render(await ProjectPage({ params }));

// Optionally, verify each button individually
screen.getAllByText("Visit").forEach((button) => {
expect(button).toBeInTheDocument();
expect(container).toHaveTextContent("Project not found!");
});

it("renders mobile and desktop layouts correctly", async () => {
const params = Promise.resolve({ id: "1" });
const { container } = render(await ProjectPage({ params }));

const mobileButton = container.querySelector(".sm\\:hidden");
expect(mobileButton).toBeInTheDocument();

const desktopButton = container.querySelector(".hidden.sm\\:block");
expect(desktopButton).toBeInTheDocument();
});
});

describe("generateMetadata", () => {
beforeEach(() => {
jest.clearAllMocks();
mockFetch.mockResolvedValue({
ok: true,
json: async () => mockApiResponse,
});
});

// Check if similar projects section is rendered
expect(screen.getByTestId("similar-projects")).toBeInTheDocument();
it("generates correct metadata for existing project", async () => {
const params = Promise.resolve({ id: "1" });
const metadata = await generateMetadata({ params });

expect(metadata).toEqual({
title: "Test Project",
description: "Test Description",
metadataBase: new URL("https://cardanoapi.io"),
openGraph: {
title: "Test Project",
description: "Test Description",
images: [
{
url: `/api/og?title=${encodeURIComponent(
"Test Project"
)}&description=${encodeURIComponent("Test Description")}`,
width: 1200,
height: 630,
},
],
},
});
});

it("returns empty metadata for missing ID", async () => {
const params = Promise.resolve({ id: "" });
const metadata = await generateMetadata({ params });
expect(metadata).toEqual({});
});

it("returns empty metadata for non-existent project", async () => {
const params = Promise.resolve({ id: "nonexistent" });
mockFetch.mockResolvedValue({
ok: true,
json: async () => ({ projects: [], results: 0, status: "success" }),
});

const metadata = await generateMetadata({ params });
expect(metadata).toEqual({});
});

it("uses default values when project fields are missing", async () => {
const params = Promise.resolve({ id: "1" });
mockFetch.mockResolvedValue({
ok: true,
json: async () => ({
projects: [
{
...mockProject,
projectname: undefined,
description: undefined,
},
],
results: 1,
status: "success",
}),
});

const metadata = await generateMetadata({ params });
expect(metadata.title).toBe("Cardano API");
expect(metadata.description).toBe("A List of Cardano API Projects");
});
});
Loading