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
65 changes: 65 additions & 0 deletions tasks/crud/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# CRUD Service for File Metadata

This project provides a simple CRUD (Create, Read, Update, Delete) API for managing file metadata using Go, MongoDB, and Gorilla Mux. Below is a description of the main files in the `crud` directory:

## File Overview

### main.go
- Entry point for the application.
- Loads environment variables, connects to MongoDB, and sets up HTTP routes.
- Registers endpoints for health check and all file operations (create, read, update, delete).

### usecase/filemetadata.go
- Contains the `FileService` struct and handler methods for each API endpoint.
- Implements business logic for creating, retrieving, updating, and deleting file metadata.
- Handles HTTP requests and responses.

### repository/filemetadata.go
- Defines the `FileMetadataRepo` struct and methods for interacting with MongoDB.
- Implements database operations for file metadata (insert, find, update, delete).

### repository/filemetadata_test.go
- Contains unit tests for the repository layer.
- Tests CRUD operations against a test MongoDB collection.

### model/filemetadata.go
- Defines the `FileMetadata` struct representing the file metadata schema.
- Used by both the usecase and repository layers.

## How to Run

1. Set up a `.env` file with the following variables:
- `MONGO_URI`: MongoDB connection string
- `DB_NAME`: Database name
- `COLLECTION_NAME`: Collection name
2. Run `go mod tidy` to install dependencies.
3. Start the server:
```sh
go run main.go
```
4. The API will be available at `http://localhost:4444`.

## Endpoints
- `GET /health` β€” Health check
- `POST /file` β€” Create a new file metadata entry
- `GET /file/{id}` β€” Get file metadata by ID
- `GET /file` β€” Get all file metadata
- `PUT /file/{id}` β€” Update file metadata by ID
- `DELETE /file/{id}` β€” Delete file metadata by ID
- `DELETE /file` β€” Delete all file metadata

## Dependencies
- [Gorilla Mux](https://github.com/gorilla/mux)
- [MongoDB Go Driver](https://go.mongodb.org/mongo-driver)
- [godotenv](https://github.com/joho/godotenv)

---


For more details, see comments in each file or contact the project maintainer.

## Testing
![alt text](image.png)
![alt text](image-2.png)
![alt text](image-3.png)
![alt text](image-4.png)
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 github.com/centauri1219/filenest/tasks/crud

go 1.24.4

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 v0.0.4 // 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.26.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/text v0.17.0 // indirect
)
54 changes: 54 additions & 0 deletions tasks/crud/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw=
go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Binary file added tasks/crud/image-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tasks/crud/image-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tasks/crud/image-4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tasks/crud/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
65 changes: 65 additions & 0 deletions tasks/crud/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package main

import (
"context"
"log"
"net/http"
"os"

"github.com/centauri1219/filenest/tasks/crud/usecase"
"github.com/gorilla/mux"
"github.com/joho/godotenv"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readpref"
)

var mongoClient *mongo.Client

func init() { //initiate the mongo conneciton
//load .env file
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file", err)
}
log.Println("Environment variables loaded successfully ")

//create mongo client
mongoClient, err = mongo.Connect(context.Background(), options.Client().ApplyURI(os.Getenv("MONGO_URI"))) //mongo connect only tells if the connection string is correct
//context.background() is used to create a context for the connection, which can be used to cancel the operation if needed and manage its lifetime
if err != nil {
log.Fatal("Error connecting to MongoDB", err)
}

err = mongoClient.Ping(context.Background(), readpref.Primary()) //sends request to mongo client to verify if it can connect to deployment
if err != nil {
log.Fatal("Error pinging MongoDB", err)
}

log.Println("MongoDB connected successfully")
}

func main() {
defer mongoClient.Disconnect(context.Background()) //close mongo connection
coll := mongoClient.Database(os.Getenv("DB_NAME")).Collection(os.Getenv("COLLECTION_NAME")) //coll is a reference to the mongodb collection using mongoclient
//create file service
fileservice := usecase.FileService{MongoCollection: coll} //fileservice is an object that provides file management logic, backed by mongodb

//create router
r := mux.NewRouter() //directs incoming http requests to the handler funcs
r.HandleFunc("/health", healthHandler).Methods(http.MethodGet) //check if server is running
r.HandleFunc("/file", fileservice.CreateFile).Methods(http.MethodPost)
r.HandleFunc("/file/{id}", fileservice.GetFilebyID).Methods(http.MethodGet)
r.HandleFunc("/file", fileservice.GetAllFiles).Methods(http.MethodGet)
r.HandleFunc("/file/{id}", fileservice.UpdateFilebyID).Methods(http.MethodPut)
r.HandleFunc("/file/{id}", fileservice.DeleteFilebyID).Methods(http.MethodDelete)
r.HandleFunc("/file", fileservice.DeleteAll).Methods(http.MethodDelete)

log.Println("Server is running on port 4444")
http.ListenAndServe(":4444", r)
}

func healthHandler(w http.ResponseWriter, r *http.Request) { //mapped to /health endpoint
w.WriteHeader(http.StatusOK) //http response
w.Write([]byte("running....")) //write response to client
}
15 changes: 15 additions & 0 deletions tasks/crud/model/filemetadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package model

import (
"time"
)

type FileMetadata struct {
ID int `json:"id" bson:"id"`
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"`
UpdatedAt time.Time `json:"updated_at" bson:"updated_at"`
}
84 changes: 84 additions & 0 deletions tasks/crud/repository/filemetadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package repository

import (
"context"
"fmt"

"github.com/centauri1219/filenest/tasks/crud/model"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
)

type FileMetadataRepo struct { //repository layer
MongoCollection *mongo.Collection //holds a reference to the MongoDB collection on which we point our operations

}

func (r *FileMetadataRepo) InsertFile(fil *model.FileMetadata) (interface{}, error) { //interface is the object id thatll be created by mongodb
result, err := r.MongoCollection.InsertOne(context.Background(), fil) //again, context.background() is used to create a context for the operation, which can be used to cancel the operation if needed and manage its lifetime
if err != nil {
return nil, err
}

return result.InsertedID, nil
}

func (r *FileMetadataRepo) FindFilebyID(fileID int) (*model.FileMetadata, error) {
var file model.FileMetadata // we have to covert bson to struct field model.filemetadata
err := r.MongoCollection.FindOne(context.Background(),
bson.D{{Key: "id", Value: fileID}}).Decode(&file)

if err != nil {
return nil, err
}

return &file, nil

}

func (r *FileMetadataRepo) FindAllFiles() ([]model.FileMetadata, error) {
results, err := r.MongoCollection.Find(context.Background(), bson.D{}) //creates cursor to iterate over all documents in the collection
if err != nil {
return nil, err
}
var files []model.FileMetadata
err = results.All(context.Background(), &files) //decodes and stores in files slice
if err != nil {
return nil, fmt.Errorf("results decode error %s", err.Error())
}

return files, nil
}

func (r *FileMetadataRepo) UpdateFilebyID(fileID int, updatefile *model.FileMetadata) (int64, error) {
result, err := r.MongoCollection.UpdateOne(context.Background(),
bson.D{{Key: "id", Value: fileID}}, //old file metadata will be searched by id
bson.D{{Key: "$set", Value: updatefile}}) //new file metadata will replace the old one

if err != nil {
return 0, err
}

return result.ModifiedCount, nil //modified count: if you update the same doc again and again itll return 0
}

func (r *FileMetadataRepo) DeleteFilebyID(fileID int) (int64, error) {
result, err := r.MongoCollection.DeleteOne(context.Background(),
bson.D{{Key: "id", Value: fileID}})
if err != nil {
return 0, err
}

return result.DeletedCount, nil
}

func (r *FileMetadataRepo) DeleteAll() (int64, error) {
results, err := r.MongoCollection.DeleteMany(context.Background(),
bson.D{{}})

if err != nil {
return 0, err
}

return results.DeletedCount, nil
}
Loading