Add OpenAPI products API

This commit is contained in:
Benaya Nathanael Yeroham 2026-09-02 16:58:57 +07:00
commit cd72a98d70
5 changed files with 309 additions and 0 deletions

4
.env.example Normal file
View File

@ -0,0 +1,4 @@
# Copy file ini menjadi .env dan isi dengan connection string Neon kamu
# Dapatkan connection string dari dashboard Neon: Overview → Connection Details
DATABASE_URL=postgresql://username:password@ep-xxx-xxx-123456.us-east-2.aws.neon.tech/neondb?sslmode=require

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.env
__pycache__/
*.pyc

121
app.py Normal file
View File

@ -0,0 +1,121 @@
"""
Backend Flask — Products API
"""
import os
import psycopg
from flask import Flask, jsonify
app = Flask(__name__)
def get_db_connection():
"""Membuat koneksi ke database Neon PostgreSQL."""
return psycopg.connect(os.environ["DATABASE_URL"])
@app.get("/")
def home():
"""Cek apakah server hidup."""
return jsonify({"status": "ok", "message": "Products API is running"})
@app.get("/products")
def get_products():
"""Mengambil semua produk dari database."""
conn = get_db_connection()
cur = conn.cursor()
cur.execute("SELECT id, name, price, stock FROM products ORDER BY id")
rows = cur.fetchall()
cur.close()
conn.close()
products = [
{"id": r[0], "name": r[1], "price": float(r[2]), "stock": r[3]}
for r in rows
]
return jsonify(products)
@app.get("/products/<int:product_id>")
def get_product_by_id(product_id):
"""Mengambil satu produk berdasarkan ID."""
conn = get_db_connection()
cur = conn.cursor()
cur.execute(
"SELECT id, name, price, stock FROM products WHERE id = %s",
(product_id,),
)
row = cur.fetchone()
cur.close()
conn.close()
if row is None:
return jsonify({"error": "Product not found"}), 404
product = {
"id": row[0],
"name": row[1],
"price": float(row[2]),
"stock": row[3],
}
return jsonify(product)
@app.post("/products")
def create_product():
"""Menambah produk baru ke database."""
from flask import request
if not request.is_json:
return jsonify({"error": "Request must be JSON"}), 400
data = request.get_json()
for field in ("name", "price", "stock"):
if field not in data:
return jsonify({"error": f"{field} is required"}), 400
conn = get_db_connection()
cur = conn.cursor()
try:
cur.execute(
"INSERT INTO products (name, price, stock) VALUES (%s, %s, %s) "
"RETURNING id, name, price, stock",
(data["name"], data["price"], data["stock"]),
)
row = cur.fetchone()
conn.commit()
except Exception:
conn.rollback()
return jsonify({"error": "Invalid input"}), 400
finally:
cur.close()
conn.close()
product = {
"id": row[0],
"name": row[1],
"price": float(row[2]),
"stock": row[3],
}
return jsonify(product), 201
@app.delete("/products/<int:product_id>")
def delete_product(product_id):
"""Menghapus satu produk berdasarkan ID."""
conn = get_db_connection()
cur = conn.cursor()
cur.execute("SELECT id FROM products WHERE id = %s", (product_id,))
if cur.fetchone() is None:
cur.close()
conn.close()
return jsonify({"error": "Product not found"}), 404
cur.execute("DELETE FROM products WHERE id = %s", (product_id,))
conn.commit()
cur.close()
conn.close()
return "", 204

178
openapi.yaml Normal file
View File

@ -0,0 +1,178 @@
openapi: 3.0.3
info:
title: Products API
version: 1.0.0
description: API untuk mengelola data products pada database Neon
servers:
- url: https://your-api-url.vercel.app
description: Vercel (sesuaikan dengan URL deploy kamu)
paths:
/products:
get:
summary: Get all products
operationId: getProducts
description: Mengambil semua produk dari database
tags:
- Products
responses:
'200':
description: Daftar semua produk
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Product'
post:
summary: Create a new product
operationId: createProduct
description: Menambah produk baru ke database
tags:
- Products
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ProductInput'
responses:
'201':
description: Produk berhasil dibuat
content:
application/json:
schema:
$ref: '#/components/schemas/Product'
'400':
description: Input tidak valid
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/products/{id}:
get:
summary: Get product by ID
operationId: getProductById
description: Mengambil satu produk berdasarkan ID
tags:
- Products
parameters:
- name: id
in: path
required: true
description: ID produk
schema:
type: integer
responses:
'200':
description: Produk ditemukan
content:
application/json:
schema:
$ref: '#/components/schemas/Product'
'400':
description: ID tidak valid
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: Produk tidak ditemukan
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
delete:
summary: Delete a product
operationId: deleteProduct
description: Menghapus satu produk berdasarkan ID
tags:
- Products
parameters:
- name: id
in: path
required: true
description: ID produk
schema:
type: integer
responses:
'204':
description: Produk berhasil dihapus
'400':
description: ID tidak valid
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: Produk tidak ditemukan
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
schemas:
Product:
type: object
description: Representasi satu produk dari database
properties:
id:
type: integer
description: ID unik produk, auto-generated oleh database
example: 1
name:
type: string
description: Nama produk
maxLength: 100
example: "Laptop"
price:
type: number
description: Harga produk
example: 8500000.00
stock:
type: integer
description: Jumlah stok produk
example: 10
required:
- id
- name
- price
- stock
ProductInput:
type: object
description: Schema untuk request body saat menambahkan produk baru
properties:
name:
type: string
description: Nama produk
maxLength: 100
example: "Monitor"
price:
type: number
description: Harga produk
example: 1500000.00
stock:
type: integer
description: Jumlah stok produk
example: 5
required:
- name
- price
- stock
Error:
type: object
description: Schema untuk response error
properties:
error:
type: string
description: Pesan error
example: "Product not found"
required:
- error

3
requirements.txt Normal file
View File

@ -0,0 +1,3 @@
Flask>=3.0.0
psycopg>=3.1.0
python-dotenv>=1.0.0