Skip to content
Open
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
144 changes: 144 additions & 0 deletions tasks/crud/CRUD_API_README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@

# πŸ“¦ CRUD API with Golang, MongoDB & Gorilla Mux

A lightweight, performant RESTful API in Go designed to handle basic Create, Read, Update, and Delete (CRUD) operations over file metadata using MongoDB as the database and Gorilla Mux as the router.

---

## 🌟 Overview

This project implements a modular and extensible file metadata management service. It provides clean REST endpoints to perform CRUD operations on a MongoDB collection. Designed to be simple yet scalable, the API can be easily integrated into larger systems such as content managers, file explorers, or intelligent search frameworks like FileNest.

---

## 🎯 Key Features

- βš™οΈ RESTful API with proper HTTP methods and status codes
- πŸ›‘οΈ Input validation and error handling
- πŸ—ƒοΈ MongoDB integration with BSON support for efficient storage
- πŸ”„ Endpoints for Create, Read (all/single), Update, and Delete operations
- πŸ“Ž Built with modular code for easy extension
- πŸ“« Tested with Postman and Curl

---

## πŸ—οΈ Project Architecture

```
crud-api/
β”œβ”€β”€ controllers/ # API logic and handler functions
β”œβ”€β”€ models/ # Data model (MongoDB schema)
β”œβ”€β”€ router/ # Gorilla Mux routing definitions
β”œβ”€β”€ .env # MongoDB credentials and configs
β”œβ”€β”€ go.mod # Module dependencies
β”œβ”€β”€ go.sum # Module checksums
β”œβ”€β”€ main.go # Application entrypoint
└── README.md # This file
```

---

## βš™οΈ Tech Stack

| Layer | Tech |
|----------------|-------------------------------|
| Language | Golang 1.21+ |
| Router | Gorilla Mux |
| Database | MongoDB Atlas (or local) |
| Driver | Mongo Go Driver |
| Testing Tool | Postman, Curl |

---

## πŸš€ Quick Start

### πŸ“‹ Prerequisites

- Go 1.21 or higher
- MongoDB Atlas or local MongoDB instance
- Git

### πŸ“₯ Installation

```bash
# Download Go dependencies
go mod tidy
```

### βš™οΈ Environment Setup

Create a `.env` file in the root with the following:

```
MONGO_URI=mongodb+srv://<username>:<password>@cluster0.mongodb.net/?retryWrites=true&w=majority
```

### ▢️ Run the Server

```bash
go run main.go
```

> Server starts on `http://localhost:8000`

---

## πŸ“‘ API Endpoints

| Method | Endpoint | Description |
|--------|---------------------|------------------------------|
| GET | `/api/files` | Get all file records |
| GET | `/api/files/{id}` | Get file by ID |
| POST | `/api/files` | Create new file record |
| PUT | `/api/files/{id}` | Update file by ID |
| DELETE | `/api/files/{id}` | Delete file by ID |

---

## πŸ“„ Data Model (FileMetadata)

```go
type FileMetadata struct {
ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"`
FileName string `json:"filename" bson:"filename"`
FilePath string `json:"filepath" bson:"filepath"`
FileSize int64 `json:"filesize" bson:"filesize"`
ContentType string `json:"content_type" bson:"content_type"`
CreatedAt time.Time `json:"created_at" bson:"created_at"`
}
```

---

## πŸ§ͺ Testing

Use [Postman](https://www.postman.com/) or Curl:

### Create File (POST)

```bash
curl -X POST http://localhost:8000/api/files \
-H "Content-Type: application/json" \
-d '{"filename":"test.pdf","filepath":"/docs/test.pdf","filesize":1024,"content_type":"application/pdf"}'
```

### Get All Files (GET)

```bash
curl http://localhost:8000/api/files
```

### Update File (PUT)

```bash
curl -X PUT http://localhost:8000/api/files/<id> \
-H "Content-Type: application/json" \
-d '{"filename":"updated.pdf"}'
```

### Delete File (DELETE)

```bash
curl -X DELETE http://localhost:8000/api/files/<id>
```

205 changes: 205 additions & 0 deletions tasks/crud/controllers/filecontroller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
package controllers

import (
"context"
"crud-api/models"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"time"

"github.com/gorilla/mux"
"github.com/joho/godotenv"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)

const (
dbName = "FileDb"
colName = "Files"
)

var collection *mongo.Collection

// Initializes MongoDB connection and sets the collection.
func init() {
if err := godotenv.Load(); err != nil {
log.Fatal("Error loading .env file")
}

connectionString := os.Getenv("MONGODB_URI")
clientOptions := options.Client().ApplyURI(connectionString)

client, err := mongo.Connect(context.TODO(), clientOptions)
if err != nil {
log.Fatal("Mongo connection error:", err)
}

fmt.Println("MongoDB connection successful")
collection = client.Database(dbName).Collection(colName)
}

// Inserts a new file document into MongoDB.
func createFile(file *models.FileMetadata) error {
file.ID = primitive.NewObjectID()
file.CreatedAt = time.Now()
file.UpdatedAt = time.Now()

_, err := collection.InsertOne(context.Background(), file)
return err
}

// Updates a file document by ID with non-empty fields and returns updated doc.
func updateFile(file *models.FileMetadata, id string) error {
objID, err := primitive.ObjectIDFromHex(id)
if err != nil {
return fmt.Errorf("invalid ID: %v", err)
}

updateFields := bson.M{}
if file.FileName != "" {
updateFields["filename"] = file.FileName
}
if file.FilePath != "" {
updateFields["filepath"] = file.FilePath
}
if file.FileSize != 0 {
updateFields["filesize"] = file.FileSize
}
if file.ContentType != "" {
updateFields["content_type"] = file.ContentType
}
updateFields["updated_at"] = time.Now()

_, err = collection.UpdateOne(context.Background(), bson.M{"_id": objID}, bson.M{"$set": updateFields})
if err != nil {
return err
}

return collection.FindOne(context.Background(), bson.M{"_id": objID}).Decode(file)
}

// Deletes a single file document by ID.
func deleteOneFile(fileID string) error {
objID, err := primitive.ObjectIDFromHex(fileID)
if err != nil {
return err
}

_, err = collection.DeleteOne(context.Background(), bson.M{"_id": objID})
return err
}

// Retrieves all file documents from the collection.
func getAllFiles() ([]models.FileMetadata, error) {
cursor, err := collection.Find(context.Background(), bson.M{})
if err != nil {
return nil, err
}
defer cursor.Close(context.Background())

var files []models.FileMetadata
for cursor.Next(context.Background()) {
var file models.FileMetadata
if err := cursor.Decode(&file); err != nil {
log.Println("Decode error:", err)
continue
}
files = append(files, file)
}

return files, nil
}

// Retrieves a single file document by ID.
func getFileByID(fileID string) (*models.FileMetadata, error) {
objID, err := primitive.ObjectIDFromHex(fileID)
if err != nil {
return nil, err
}

var file models.FileMetadata
err = collection.FindOne(context.Background(), bson.M{"_id": objID}).Decode(&file)
return &file, err
}

// HTTP: Returns all files.
func GetAllFiles(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")

files, err := getAllFiles()
if err != nil {
http.Error(w, "Failed to fetch files", http.StatusInternalServerError)
return
}

json.NewEncoder(w).Encode(files)
}

// HTTP: Returns a file by ID.
func GetFile(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
id := mux.Vars(r)["id"]

file, err := getFileByID(id)
if err != nil {
http.Error(w, "File not found", http.StatusNotFound)
return
}

json.NewEncoder(w).Encode(file)
}

// HTTP: Creates a new file.
func CreateFile(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")

var file models.FileMetadata
if err := json.NewDecoder(r.Body).Decode(&file); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}

if err := createFile(&file); err != nil {
http.Error(w, "Failed to create file", http.StatusInternalServerError)
return
}

json.NewEncoder(w).Encode(file)
}

// HTTP: Updates a file by ID.
func UpdateFile(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
id := mux.Vars(r)["id"]

var file models.FileMetadata
if err := json.NewDecoder(r.Body).Decode(&file); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}

if err := updateFile(&file, id); err != nil {
http.Error(w, "Failed to update file", http.StatusInternalServerError)
return
}

json.NewEncoder(w).Encode(file)
}

// HTTP: Deletes a file by ID.
func DeleteFile(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
id := mux.Vars(r)["id"]

if err := deleteOneFile(id); err != nil {
http.Error(w, "Failed to delete file", http.StatusInternalServerError)
return
}

json.NewEncoder(w).Encode(map[string]string{"deleted_id": id})
}
22 changes: 22 additions & 0 deletions tasks/crud/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module crud-api

go 1.22.2

require (
github.com/gorilla/mux v1.8.1
github.com/joho/godotenv v1.5.1
go.mongodb.org/mongo-driver v1.17.4
)

require (
github.com/golang/snappy v1.0.0 // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
golang.org/x/crypto v0.33.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/text v0.22.0 // indirect
)
Loading