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
7 changes: 7 additions & 0 deletions oceanbase-nextjs-vercel-demo/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# OceanBase Cloud database connection
# Get values from: OceanBase Cloud console → your cluster → connection info
# Format: mysql://username:password@host:port/database

DATABASE_URL=mysql://<User>:<Password>@<Endpoint>:<Port>/<Database>

# Copy this file to .env and fill in your values for local development or Vercel
4 changes: 4 additions & 0 deletions oceanbase-nextjs-vercel-demo/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "next/core-web-vitals"
}

53 changes: 53 additions & 0 deletions oceanbase-nextjs-vercel-demo/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
yarn.lock

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem
*.log

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

# local env files
.env*.local
.env
.env.development.local
.env.test.local
.env.production.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

# IDE
.vscode/
.idea/
*.swp
*.swo
*~

# OS
Thumbs.db

57 changes: 57 additions & 0 deletions oceanbase-nextjs-vercel-demo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# UniSelect Course Demo

**UniSelect Course** is a university course selection management system. Use it to manage course information, view course details, add and delete reviews, and edit course capacity.

Built with **Next.js**, **OceanBase Cloud** , and deployable on **Vercel**.

## Demo

![UniSelect Course screenshot](https://github.com/user-attachments/assets/d1fa46a9-cbe7-4b62-9015-f053da41ac5b)

**Live demo:** [Open the app](https://oceanbase-nextjs-vercel-demo.vercel.app)

## Deploy to Vercel

You can deploy this demo with one click if you already have an OceanBase database (or create one after provisioning through [OceanBase Cloud](https://www.oceanbase.com/product/cloud)).

**Quick deployment**:

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Foceanbase%2Fecology-plugins%2Ftree%2Fmain%2Ffluent-plugin-oceanbase-logs&project-name=OceanBase%20Cloud%20Starter&repository-name=oceanbase-cloud-starter&integration-ids=oac_kzJzQ0seDkU8FXrt6cgoec48&demo-title=OceanBase%20Cloud%20Starter&demo-description=A%20university%20course%20selection%20management%20system%20built%20with%20Next.js%20and%20OceanBase&demo-url=https%3A%2F%2Foceanbase-nextjs-vercel-demo.vercel.app%2F&demo-image=https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2Fd1fa46a9-cbe7-4b62-9015-f053da41ac5b)

## Local setup

### Install dependencies

```bash
npm install
```

### Environment

Copy the example env file and fill in your OceanBase connection string (from the [OceanBase Cloud console](https://www.oceanbase.com/product/cloud) → your cluster → connection info):

```bash
cp .env.example .env
```

```env
DATABASE_URL=mysql://<User>:<Password>@<Endpoint>:<Port>/<Database>
```

### Run the dev server

```bash
npm run dev
```

Open [http://localhost:3000](http://localhost:3000) in your browser.

## Learn more

- [OceanBase documentation](https://www.oceanbase.com/docs)
- [Vercel documentation](https://vercel.com/docs)
- [Next.js documentation](https://nextjs.org/docs)

## License

MIT
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { NextRequest, NextResponse } from 'next/server';
import { query } from '@/lib/db';

// PUT - update course capacity
export async function PUT(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const body = await request.json();
const { capacity } = body;

if (!capacity || capacity < 0) {
return NextResponse.json(
{ success: false, error: 'Valid capacity is required' },
{ status: 400 }
);
}

await query(
'UPDATE `courses` SET `capacity` = ? WHERE `id` = ?',
[capacity, params.id]
);

const courses = await query(
'SELECT `id`, `code`, `name`, `capacity`, `enrolled` FROM `courses` WHERE `id` = ?',
[params.id]
);

const courseArray = courses as any[];
return NextResponse.json({
success: true,
data: courseArray[0],
message: 'Course capacity updated successfully',
});
} catch (error: any) {
return NextResponse.json(
{ success: false, error: error.message },
{ status: 500 }
);
}
}

70 changes: 70 additions & 0 deletions oceanbase-nextjs-vercel-demo/app/api/courses/[id]/reviews/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { NextRequest, NextResponse } from 'next/server';
import { query } from '@/lib/db';

// GET - list reviews for course
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const reviews = await query(
'SELECT `id`, `course_id`, `student_name`, `rating`, `comment`, `created_at` FROM `reviews` WHERE `course_id` = ? ORDER BY `created_at` DESC',
[params.id]
);

return NextResponse.json({ success: true, data: reviews });
} catch (error: any) {
return NextResponse.json(
{ success: false, error: error.message },
{ status: 500 }
);
}
}

// POST - add review
export async function POST(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const body = await request.json();
const { student_name, rating, comment } = body;

if (!student_name || !rating) {
return NextResponse.json(
{ success: false, error: 'Student name and rating are required' },
{ status: 400 }
);
}

if (rating < 1 || rating > 5) {
return NextResponse.json(
{ success: false, error: 'Rating must be between 1 and 5' },
{ status: 400 }
);
}

const result = await query(
'INSERT INTO `reviews` (`course_id`, `student_name`, `rating`, `comment`) VALUES (?, ?, ?, ?)',
[params.id, student_name, rating, comment || null]
);

const insertResult = result as any;
return NextResponse.json({
success: true,
data: {
id: insertResult.insertId,
course_id: parseInt(params.id),
student_name,
rating,
comment,
},
});
} catch (error: any) {
return NextResponse.json(
{ success: false, error: error.message },
{ status: 500 }
);
}
}

47 changes: 47 additions & 0 deletions oceanbase-nextjs-vercel-demo/app/api/courses/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { NextRequest, NextResponse } from 'next/server';
import { query } from '@/lib/db';

// GET - course by id
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const courses = await query(
'SELECT `id`, `code`, `name`, `description`, `instructor`, `department`, `credits`, `capacity`, `enrolled`, `semester` FROM `courses` WHERE `id` = ?',
[params.id]
);

const courseArray = courses as any[];
if (courseArray.length === 0) {
return NextResponse.json(
{ success: false, error: 'Course not found' },
{ status: 404 }
);
}

return NextResponse.json({ success: true, data: courseArray[0] });
} catch (error: any) {
return NextResponse.json(
{ success: false, error: error.message },
{ status: 500 }
);
}
}

// DELETE - remove course
export async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
await query('DELETE FROM `courses` WHERE `id` = ?', [params.id]);
return NextResponse.json({ success: true, message: 'Course deleted successfully' });
} catch (error: any) {
return NextResponse.json(
{ success: false, error: error.message },
{ status: 500 }
);
}
}

76 changes: 76 additions & 0 deletions oceanbase-nextjs-vercel-demo/app/api/courses/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { NextRequest, NextResponse } from 'next/server';
import { query } from '@/lib/db';

// GET - list courses
export async function GET(request: NextRequest) {
try {
const searchParams = request.nextUrl.searchParams;
const department = searchParams.get('department');
const semester = searchParams.get('semester');

let sql = 'SELECT `id`, `code`, `name`, `description`, `instructor`, `department`, `credits`, `capacity`, `enrolled`, `semester` FROM `courses`';
const params: any[] = [];

if (department || semester) {
const conditions: string[] = [];
if (department) {
conditions.push('`department` = ?');
params.push(department);
}
if (semester) {
conditions.push('`semester` = ?');
params.push(semester);
}
sql += ' WHERE ' + conditions.join(' AND ');
}

sql += ' ORDER BY `code` ASC';

const courses = await query(sql, params);
return NextResponse.json({ success: true, data: courses });
} catch (error: any) {
return NextResponse.json(
{ success: false, error: error.message },
{ status: 500 }
);
}
}

// POST - create course
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { code, name, description, instructor, department, credits, capacity, semester } = body;

if (!code || !name || !instructor || !department) {
return NextResponse.json(
{ success: false, error: 'Code, name, instructor, and department are required' },
{ status: 400 }
);
}

const result = await query(
`INSERT INTO \`courses\` (\`code\`, \`name\`, \`description\`, \`instructor\`, \`department\`, \`credits\`, \`capacity\`, \`semester\`)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
[code, name, description || null, instructor, department, credits || 3, capacity || 30, semester || 'Fall 2024']
);

const insertResult = result as any;
return NextResponse.json({
success: true,
data: { id: insertResult.insertId, code, name, instructor, department },
});
} catch (error: any) {
if (error.code === 'ER_DUP_ENTRY') {
return NextResponse.json(
{ success: false, error: 'Course code already exists' },
{ status: 400 }
);
}
return NextResponse.json(
{ success: false, error: error.message },
{ status: 500 }
);
}
}

19 changes: 19 additions & 0 deletions oceanbase-nextjs-vercel-demo/app/api/reviews/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { NextRequest, NextResponse } from 'next/server';
import { query } from '@/lib/db';

// DELETE - remove a review
export async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
await query('DELETE FROM `reviews` WHERE `id` = ?', [params.id]);
return NextResponse.json({ success: true, message: 'Review deleted successfully' });
} catch (error: any) {
return NextResponse.json(
{ success: false, error: error.message },
{ status: 500 }
);
}
}

Loading
Loading