Skip to content

Commit 9569bfc

Browse files
Fredrick PaulinFredrick Paulin
authored andcommitted
Inital commit
0 parents  commit 9569bfc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+8289
-0
lines changed

.github/workflows/ci.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
pull_request:
7+
branches: [ "main" ]
8+
9+
jobs:
10+
test-backend:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
- name: Set up Go
15+
uses: actions/setup-go@v5
16+
with:
17+
go-version: '1.21'
18+
- name: Backend tests
19+
run: make test-backend
20+
21+
test-frontend:
22+
runs-on: ubuntu-latest
23+
steps:
24+
- uses: actions/checkout@v4
25+
- name: Set up Node.js
26+
uses: actions/setup-node@v4
27+
with:
28+
node-version: '20'
29+
- name: Frontend tests
30+
run: make test-frontend

.gitignore

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Node
2+
frontend/node_modules/
3+
frontend/dist/
4+
frontend/.env
5+
frontend/.env.*
6+
7+
# Go
8+
backend/bin/
9+
backend/*.exe
10+
backend/*.exe~
11+
backend/*.out
12+
backend/*.test
13+
backend/coverage.out
14+
backend/.env
15+
16+
# Database and backups
17+
data/
18+
backups/
19+
20+
# Docker
21+
*.log
22+
*.pid
23+
*.sock
24+
.docker/
25+
26+
# System
27+
.DS_Store
28+
Thumbs.db
29+
30+
# Editor/IDE
31+
.vscode/
32+
.idea/
33+
*.swp
34+
*.swo
35+
36+
# Build artifacts
37+
*.tar
38+
*.gz
39+
*.zip
40+
41+
# Misc
42+
.env
43+
.env.*
44+
45+
# Ignore local config
46+
*.local

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Makefile

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Makefile for Goose, Chicken, Guinea Fowl Egg Tracker Project
2+
3+
PROJECT_NAME=egg-tracker
4+
5+
# Directories
6+
BACKEND_DIR=backend
7+
FRONTEND_DIR=frontend
8+
DOCKER_DIR=docker
9+
BACKUP_DIR=backups
10+
11+
# Docker Compose file
12+
COMPOSE_FILE=docker-compose.yml
13+
14+
# Targets
15+
16+
## Build
17+
18+
build-backend:
19+
cd $(BACKEND_DIR) && go build -o app
20+
21+
build-frontend:
22+
cd $(FRONTEND_DIR) && npm install && npm run build
23+
24+
## Run Local Dev Servers (without Docker)
25+
26+
run-backend:
27+
cd $(BACKEND_DIR) && go run main.go
28+
29+
run-frontend:
30+
cd $(FRONTEND_DIR) && npm run dev
31+
32+
## Docker Compose
33+
34+
docker-up:
35+
docker-compose -f $(COMPOSE_FILE) up --build
36+
37+
docker-down:
38+
docker-compose -f $(COMPOSE_FILE) down
39+
40+
## Database Backup
41+
42+
backup:
43+
@echo "Creating backup..."
44+
mkdir -p $(BACKUP_DIR)
45+
cp $(BACKEND_DIR)/data/*.db $(BACKUP_DIR)/ || true
46+
cp $(BACKEND_DIR)/data/*.duckdb $(BACKUP_DIR)/ || true
47+
@echo "Backup completed."
48+
49+
## Clean
50+
51+
clean:
52+
@echo "Cleaning builds..."
53+
rm -rf $(BACKEND_DIR)/app
54+
rm -rf $(FRONTEND_DIR)/dist
55+
rm -rf $(FRONTEND_DIR)/node_modules
56+
rm -f $(FRONTEND_DIR)/.env*
57+
@echo "Clean completed."
58+
59+
## Testing
60+
61+
test-backend:
62+
cd $(BACKEND_DIR) && go test ./...
63+
64+
# test-frontend will not fail if no test script is present, but will print a warning
65+
66+
test-frontend:
67+
cd $(FRONTEND_DIR) && if npm run | grep -q " test"; then npm run test; else echo "No test script found in package.json"; fi
68+
69+
## Help
70+
71+
help:
72+
@echo ""
73+
@echo "Available commands:"
74+
@echo " make build-backend Build the Go backend"
75+
@echo " make build-frontend Build the React frontend"
76+
@echo " make run-backend Run backend locally"
77+
@echo " make run-frontend Run frontend locally"
78+
@echo " make docker-up Start docker-compose services"
79+
@echo " make docker-down Stop docker-compose services"
80+
@echo " make backup Backup SQLite and DuckDB databases"
81+
@echo " make clean Clean build artifacts"
82+
@echo " make test-backend Run backend Go tests"
83+
@echo " make test-frontend Run frontend tests"
84+
@echo " make help Show this help message"
85+
@echo ""

README.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Egg Tracker
2+
3+
A full-stack application for tracking eggs, inventory, and related data. Built with React (frontend), Go (backend), and DuckDB/SQLite for storage. Dockerized for easy development and deployment.
4+
5+
---
6+
7+
## Prerequisites
8+
- [Docker](https://www.docker.com/get-started)
9+
- [Docker Compose](https://docs.docker.com/compose/)
10+
11+
---
12+
13+
## Directory Structure
14+
15+
```
16+
/egg-tracker
17+
├── backend/ # Go backend source code
18+
├── frontend/ # React frontend source code
19+
├── docker/ # Dockerfiles and nginx config
20+
├── data/ # Database files (persisted)
21+
├── docker-compose.yml
22+
├── Makefile
23+
└── README.md
24+
```
25+
26+
---
27+
28+
## Setup & Usage
29+
30+
### 1. Clone the repository
31+
```
32+
git clone <your-repo-url>
33+
cd egg-tracker
34+
```
35+
36+
### 2. Build and run with Docker Compose
37+
```
38+
docker-compose up --build
39+
```
40+
Or, if you have a Makefile:
41+
```
42+
make docker-up
43+
```
44+
45+
### 3. Access the application
46+
- Frontend: [http://localhost:3000](http://localhost:3000) or `http://<your-lan-ip>:3000`
47+
- API: proxied via `/api` from the frontend (no direct access needed)
48+
49+
---
50+
51+
## Development Notes
52+
53+
- **Frontend**: React app, built and served by nginx in the frontend container.
54+
- **Backend**: Go app, listens on port 8080 inside its container.
55+
- **nginx**: Proxies `/api` requests from the frontend container to the backend container.
56+
- **CORS**: In production, all requests go through nginx, so CORS is not an issue. For local development, the backend allows common origins (see `main.go`).
57+
- **Database**: Data is persisted in the `data/` directory and mounted into the backend container.
58+
59+
---
60+
61+
## API Usage
62+
- All API requests from the frontend should use relative paths (e.g., `/api/login`).
63+
- Do not use hardcoded backend URLs in the frontend code.
64+
65+
---
66+
67+
## Production Deployment
68+
- For HTTPS, use a reverse proxy (nginx, Caddy, Traefik) with SSL certificates in front of the frontend container.
69+
- Restrict CORS origins in production to your real domain.
70+
- Set cookies with `Secure` and `SameSite=None` for HTTPS.
71+
72+
---
73+
74+
## Troubleshooting
75+
- If you get CORS or cookie issues, check your browser's dev tools and backend CORS config.
76+
- If you change service names or ports, update the nginx config and CORS settings accordingly.
77+
78+
---
79+
80+
## Security Disclaimer
81+
82+
This project is intended for hobby or homebrew use. While basic security measures are in place, it is not recommended for production or public-facing deployments. For personal or small group use, the current login component is sufficient, but do not use as-is for sensitive applications. Always enforce HTTPS and consider additional security best practices if you expand the project.
83+
84+
## Inspiration
85+
86+
This project was inspired by the need to track the egg output of my birds. By recording daily egg counts, I can observe trends over time and gain a better understanding of their laying patterns and overall health. This helps in making informed decisions about their care and management.
87+
88+
---
89+
90+
## License
91+
MIT

backend/auth/jwt.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package auth
2+
3+
import (
4+
"errors"
5+
"time"
6+
7+
"github.com/gin-gonic/gin"
8+
"github.com/golang-jwt/jwt/v5"
9+
)
10+
11+
var (
12+
AccessSecret = []byte("your-access-secret") // Replace with env/config in production
13+
RefreshSecret = []byte("your-refresh-secret") // Replace with env/config in production
14+
)
15+
16+
func GenerateAccessToken(userID int64) (string, error) {
17+
claims := jwt.MapClaims{
18+
"user_id": userID,
19+
"exp": time.Now().Add(15 * time.Minute).Unix(),
20+
}
21+
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
22+
return token.SignedString(AccessSecret)
23+
}
24+
25+
func GenerateRefreshToken(userID int64) (string, error) {
26+
claims := jwt.MapClaims{
27+
"user_id": userID,
28+
"exp": time.Now().Add(7 * 24 * time.Hour).Unix(),
29+
}
30+
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
31+
return token.SignedString(RefreshSecret)
32+
}
33+
34+
func ParseRefreshToken(tokenStr string) (int64, error) {
35+
token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
36+
return RefreshSecret, nil
37+
})
38+
if err != nil || !token.Valid {
39+
return 0, errors.New("invalid refresh token")
40+
}
41+
claims, ok := token.Claims.(jwt.MapClaims)
42+
if !ok {
43+
return 0, errors.New("invalid claims")
44+
}
45+
userID, ok := claims["user_id"].(float64)
46+
if !ok {
47+
return 0, errors.New("invalid user_id")
48+
}
49+
return int64(userID), nil
50+
}
51+
52+
func ParseAccessToken(tokenStr string) (int64, error) {
53+
token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
54+
return AccessSecret, nil
55+
})
56+
if err != nil || !token.Valid {
57+
return 0, errors.New("invalid access token")
58+
}
59+
claims, ok := token.Claims.(jwt.MapClaims)
60+
if !ok {
61+
return 0, errors.New("invalid claims")
62+
}
63+
userID, ok := claims["user_id"].(float64)
64+
if !ok {
65+
return 0, errors.New("invalid user_id")
66+
}
67+
return int64(userID), nil
68+
}
69+
70+
// Gin middleware to protect routes
71+
// Usage: router.Use(auth.AuthMiddleware())
72+
func AuthMiddleware() func(c *gin.Context) {
73+
return func(c *gin.Context) {
74+
tokenStr := c.GetHeader("Authorization")
75+
if len(tokenStr) > 7 && tokenStr[:7] == "Bearer " {
76+
tokenStr = tokenStr[7:]
77+
}
78+
userID, err := ParseAccessToken(tokenStr)
79+
if err != nil {
80+
c.AbortWithStatus(401)
81+
return
82+
}
83+
c.Set("user_id", userID)
84+
c.Next()
85+
}
86+
}

0 commit comments

Comments
 (0)