I am batman
This commit is contained in:
33
services/auth-service/Dockerfile
Normal file
33
services/auth-service/Dockerfile
Normal file
@@ -0,0 +1,33 @@
|
||||
FROM golang:1.24-alpine AS build
|
||||
RUN apk add --no-cache git ca-certificates protobuf-dev
|
||||
WORKDIR /app
|
||||
|
||||
# use repo-level go.mod so modules are resolvable (build.context must be repo root)
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
# install Go protoc plugins
|
||||
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@latest && \
|
||||
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
||||
|
||||
# copy service sources and proto files into the image
|
||||
COPY services/auth-service ./services/auth-service
|
||||
COPY proto ./proto
|
||||
|
||||
# generate proto files
|
||||
RUN mkdir -p proto/auth proto/user proto/post && \
|
||||
export PATH=/go/bin:$PATH && \
|
||||
protoc --go_out=proto --go_opt=paths=source_relative \
|
||||
--go-grpc_out=proto --go-grpc_opt=paths=source_relative \
|
||||
--proto_path=proto \
|
||||
proto/auth.proto proto/user.proto proto/post.proto && \
|
||||
ls -la proto/auth/ proto/user/ proto/post/
|
||||
|
||||
# build a static binary for the auth service
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /usr/local/bin/auth-service ./services/auth-service/cmd
|
||||
|
||||
FROM alpine:3.20
|
||||
RUN apk add --no-cache ca-certificates
|
||||
COPY --from=build /usr/local/bin/auth-service /usr/local/bin/auth-service
|
||||
EXPOSE 50051
|
||||
ENTRYPOINT ["/usr/local/bin/auth-service"]
|
||||
43
services/auth-service/cmd/main.go
Normal file
43
services/auth-service/cmd/main.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
pb "go-microservices/proto/auth"
|
||||
|
||||
"go-microservices/services/auth-service/internal/database"
|
||||
"go-microservices/services/auth-service/internal/repository"
|
||||
"go-microservices/services/auth-service/internal/server"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("Starting Auth Service...")
|
||||
port := os.Getenv("PORT")
|
||||
if port == "" {
|
||||
port = "50051"
|
||||
}
|
||||
lis, err := net.Listen("tcp", ":"+port)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to listen: %v", err)
|
||||
}
|
||||
|
||||
db, err := database.Init()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to init database: %v", err)
|
||||
}
|
||||
|
||||
repo := repository.NewRepository(db)
|
||||
srv := server.NewAuthServer(repo)
|
||||
|
||||
grpcServer := grpc.NewServer()
|
||||
pb.RegisterAuthServiceServer(grpcServer, srv)
|
||||
log.Printf("Auth Service listening on %s", port)
|
||||
if err := grpcServer.Serve(lis); err != nil {
|
||||
log.Fatalf("Failed to serve: %v", err)
|
||||
}
|
||||
}
|
||||
1
services/auth-service/config/config.yml
Normal file
1
services/auth-service/config/config.yml
Normal file
@@ -0,0 +1 @@
|
||||
This is the configuration file for the Auth Service microservice.
|
||||
22
services/auth-service/config/env.go
Normal file
22
services/auth-service/config/env.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Env struct {
|
||||
Port int
|
||||
JWTSecret string
|
||||
TokenDuration int
|
||||
DatabaseURL string
|
||||
RedisAddr string
|
||||
RedisPassword string
|
||||
RedisDB int
|
||||
EmailHost string
|
||||
EmailPort int
|
||||
EmailUsername string
|
||||
EmailPassword string
|
||||
EmailFrom string
|
||||
FrontendURL string
|
||||
}
|
||||
28
services/auth-service/internal/database/database.go
Normal file
28
services/auth-service/internal/database/database.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"go-microservices/services/auth-service/internal/models"
|
||||
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func Init() (*gorm.DB, error) {
|
||||
dsn := os.Getenv("DATABASE_URL")
|
||||
if dsn == "" {
|
||||
dsn = "host=localhost user=postgres password=postgres dbname=postgres port=5432 sslmode=disable TimeZone=UTC"
|
||||
}
|
||||
|
||||
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := db.AutoMigrate(&models.Auth{}, &models.Test{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
23
services/auth-service/internal/models/models.go
Normal file
23
services/auth-service/internal/models/models.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Auth struct {
|
||||
gorm.Model
|
||||
Username string `gorm:"uniqueIndex;not null"`
|
||||
Email string `gorm:"uniqueIndex;not null"`
|
||||
Password string `gorm:"not null"`
|
||||
VerificationToken string `json:"-"`
|
||||
ResetToken string `json:"-"`
|
||||
ResetTokenExpiry time.Time `json:"-"`
|
||||
Role string `gorm:"type:varchar(50);default:user" json:"role,omitempty"`
|
||||
}
|
||||
|
||||
type Test struct {
|
||||
gorm.Model
|
||||
Content string `gorm:"type:text;not null"`
|
||||
}
|
||||
63
services/auth-service/internal/repository/repository.go
Normal file
63
services/auth-service/internal/repository/repository.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"go-microservices/services/auth-service/internal/models"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Repository struct {
|
||||
DB *gorm.DB
|
||||
}
|
||||
|
||||
func NewRepository(db *gorm.DB) *Repository {
|
||||
return &Repository{DB: db}
|
||||
}
|
||||
|
||||
func (r *Repository) CreateAuth(a *models.Auth) error {
|
||||
return r.DB.Create(a).Error
|
||||
}
|
||||
|
||||
func (r *Repository) GetAuthByEmail(email string) (*models.Auth, error) {
|
||||
var a models.Auth
|
||||
if err := r.DB.Where("email = ?", email).First(&a).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &a, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetAuthByID(id uint) (*models.Auth, error) {
|
||||
var a models.Auth
|
||||
if err := r.DB.First(&a, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &a, nil
|
||||
}
|
||||
|
||||
func (r *Repository) CreateTest(t *models.Test) error {
|
||||
return r.DB.Create(t).Error
|
||||
}
|
||||
|
||||
func (r *Repository) ListTests() ([]models.Test, error) {
|
||||
var tests []models.Test
|
||||
if err := r.DB.Order("id desc").Find(&tests).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tests, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetTestByID(id uint) (*models.Test, error) {
|
||||
var t models.Test
|
||||
if err := r.DB.First(&t, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &t, nil
|
||||
}
|
||||
|
||||
func (r *Repository) UpdateTest(t *models.Test) error {
|
||||
return r.DB.Save(t).Error
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteTest(id uint) error {
|
||||
return r.DB.Delete(&models.Test{}, id).Error
|
||||
}
|
||||
210
services/auth-service/internal/server/server.go
Normal file
210
services/auth-service/internal/server/server.go
Normal file
@@ -0,0 +1,210 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
pb "go-microservices/proto/auth"
|
||||
"go-microservices/services/auth-service/internal/models"
|
||||
"go-microservices/services/auth-service/internal/repository"
|
||||
"go-microservices/services/auth-service/internal/utils"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
type AuthServer struct {
|
||||
pb.UnimplementedAuthServiceServer
|
||||
repo *repository.Repository
|
||||
}
|
||||
|
||||
func NewAuthServer(repo *repository.Repository) *AuthServer {
|
||||
return &AuthServer{repo: repo}
|
||||
}
|
||||
|
||||
func (s *AuthServer) SignUp(ctx context.Context, req *pb.SignUpRequest) (*pb.SignUpResponse, error) {
|
||||
hashed, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed to hash password: %v", err)
|
||||
}
|
||||
|
||||
auth := &models.Auth{
|
||||
Username: req.Username,
|
||||
Email: req.Email,
|
||||
Password: string(hashed),
|
||||
Role: "user", // default role (proto SignUpRequest doesn't include role)
|
||||
}
|
||||
|
||||
if err := s.repo.CreateAuth(auth); err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed to create user: %v", err)
|
||||
}
|
||||
|
||||
userID := fmt.Sprintf("%d", auth.ID)
|
||||
return &pb.SignUpResponse{UserId: userID, Message: "registered"}, nil
|
||||
}
|
||||
|
||||
func (s *AuthServer) SignIn(ctx context.Context, req *pb.SignInRequest) (*pb.SignInResponse, error) {
|
||||
auth, err := s.repo.GetAuthByEmail(req.Email)
|
||||
if err != nil || auth == nil {
|
||||
return nil, status.Errorf(codes.Unauthenticated, "invalid credentials")
|
||||
}
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(auth.Password), []byte(req.Password)); err != nil {
|
||||
return nil, status.Errorf(codes.Unauthenticated, "invalid credentials")
|
||||
}
|
||||
|
||||
accessToken, refreshToken, err := utils.GenerateJWT(*auth)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed to generate tokens: %v", err)
|
||||
}
|
||||
|
||||
userID := fmt.Sprintf("%d", auth.ID)
|
||||
return &pb.SignInResponse{
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: refreshToken,
|
||||
UserId: userID,
|
||||
Message: "logged in",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *AuthServer) ValidateToken(ctx context.Context, req *pb.ValidateTokenRequest) (*pb.ValidateTokenResponse, error) {
|
||||
claims, err := utils.ValidateJWT(req.Token, false)
|
||||
if err != nil {
|
||||
return &pb.ValidateTokenResponse{Valid: false, UserId: "", Message: "invalid token"}, nil
|
||||
}
|
||||
|
||||
var userID string
|
||||
if sub, ok := claims["sub"]; ok {
|
||||
switch v := sub.(type) {
|
||||
case string:
|
||||
userID = v
|
||||
case float64:
|
||||
userID = strconv.FormatFloat(v, 'f', 0, 64)
|
||||
default:
|
||||
userID = fmt.Sprintf("%v", v)
|
||||
}
|
||||
}
|
||||
|
||||
if userID == "" {
|
||||
return &pb.ValidateTokenResponse{Valid: false, UserId: "", Message: "invalid token"}, nil
|
||||
}
|
||||
|
||||
return &pb.ValidateTokenResponse{Valid: true, UserId: userID, Message: "valid"}, nil
|
||||
}
|
||||
|
||||
func (s *AuthServer) GetUserInfo(ctx context.Context, req *pb.GetUserInfoRequest) (*pb.GetUserInfoResponse, error) {
|
||||
// expect req.UserId (proto field user_id)
|
||||
if req.UserId == "" {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "user id required")
|
||||
}
|
||||
u64, err := strconv.ParseUint(req.UserId, 10, 64)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "invalid user id: %v", err)
|
||||
}
|
||||
user, err := s.repo.GetAuthByID(uint(u64))
|
||||
if err != nil || user == nil {
|
||||
return nil, status.Errorf(codes.NotFound, "user not found")
|
||||
}
|
||||
|
||||
return &pb.GetUserInfoResponse{
|
||||
UserId: fmt.Sprintf("%d", user.ID),
|
||||
Username: user.Username,
|
||||
Email: user.Email,
|
||||
Roles: []string{user.Role},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateTest creates a simple Test record in the database
|
||||
func (s *AuthServer) CreateTest(ctx context.Context, req *pb.CreateTestRequest) (*pb.CreateTestResponse, error) {
|
||||
if req.Content == "" {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "content required")
|
||||
}
|
||||
t := &models.Test{Content: req.Content}
|
||||
if err := s.repo.CreateTest(t); err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed to create test: %v", err)
|
||||
}
|
||||
return &pb.CreateTestResponse{
|
||||
Test: &pb.Test{
|
||||
Id: uint64(t.ID),
|
||||
Content: t.Content,
|
||||
CreatedAt: t.CreatedAt.Format(time.RFC3339),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListTests returns recent test records
|
||||
func (s *AuthServer) ListTests(ctx context.Context, req *pb.ListTestsRequest) (*pb.ListTestsResponse, error) {
|
||||
tests, err := s.repo.ListTests()
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed to list tests: %v", err)
|
||||
}
|
||||
resp := &pb.ListTestsResponse{Tests: make([]*pb.Test, 0, len(tests))}
|
||||
for _, t := range tests {
|
||||
resp.Tests = append(resp.Tests, &pb.Test{
|
||||
Id: uint64(t.ID),
|
||||
Content: t.Content,
|
||||
CreatedAt: t.CreatedAt.Format(time.RFC3339),
|
||||
})
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetTest retrieves a single test by ID
|
||||
func (s *AuthServer) GetTest(ctx context.Context, req *pb.GetTestRequest) (*pb.GetTestResponse, error) {
|
||||
if req.Id == 0 {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "test id required")
|
||||
}
|
||||
t, err := s.repo.GetTestByID(uint(req.Id))
|
||||
if err != nil || t == nil {
|
||||
return nil, status.Errorf(codes.NotFound, "test not found")
|
||||
}
|
||||
return &pb.GetTestResponse{
|
||||
Test: &pb.Test{
|
||||
Id: uint64(t.ID),
|
||||
Content: t.Content,
|
||||
CreatedAt: t.CreatedAt.Format(time.RFC3339),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateTest updates an existing test
|
||||
func (s *AuthServer) UpdateTest(ctx context.Context, req *pb.UpdateTestRequest) (*pb.UpdateTestResponse, error) {
|
||||
if req.Id == 0 {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "test id required")
|
||||
}
|
||||
if req.Content == "" {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "content required")
|
||||
}
|
||||
t, err := s.repo.GetTestByID(uint(req.Id))
|
||||
if err != nil || t == nil {
|
||||
return nil, status.Errorf(codes.NotFound, "test not found")
|
||||
}
|
||||
t.Content = req.Content
|
||||
if err := s.repo.UpdateTest(t); err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed to update test: %v", err)
|
||||
}
|
||||
return &pb.UpdateTestResponse{
|
||||
Test: &pb.Test{
|
||||
Id: uint64(t.ID),
|
||||
Content: t.Content,
|
||||
CreatedAt: t.CreatedAt.Format(time.RFC3339),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeleteTest deletes a test by ID
|
||||
func (s *AuthServer) DeleteTest(ctx context.Context, req *pb.DeleteTestRequest) (*pb.DeleteTestResponse, error) {
|
||||
if req.Id == 0 {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "test id required")
|
||||
}
|
||||
if err := s.repo.DeleteTest(uint(req.Id)); err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed to delete test: %v", err)
|
||||
}
|
||||
return &pb.DeleteTestResponse{
|
||||
Success: true,
|
||||
Message: "test deleted",
|
||||
}, nil
|
||||
}
|
||||
101
services/auth-service/internal/utils/utils.go
Normal file
101
services/auth-service/internal/utils/utils.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"go-microservices/services/auth-service/internal/models"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
|
||||
var (
|
||||
accessTokenSecret []byte
|
||||
refreshTokenSecret []byte
|
||||
)
|
||||
|
||||
func init() {
|
||||
access := os.Getenv("JWT_ACCESS_SECRET")
|
||||
if access == "" {
|
||||
access = os.Getenv("JWT_SECRET")
|
||||
}
|
||||
refresh := os.Getenv("JWT_REFRESH_SECRET")
|
||||
if refresh == "" {
|
||||
refresh = os.Getenv("JWT_SECRET")
|
||||
}
|
||||
|
||||
accessTokenSecret = []byte(access)
|
||||
refreshTokenSecret = []byte(refresh)
|
||||
}
|
||||
|
||||
// GenerateJWT generates an access token and refresh token for the provided user.
|
||||
// The function expects the provided models.Auth (or models.User) to have ID, Email and Role fields.
|
||||
func GenerateJWT(user models.Auth) (string, string, error) {
|
||||
accessClaims := jwt.MapClaims{
|
||||
"sub": user.ID,
|
||||
"email": user.Email,
|
||||
"role": user.Role,
|
||||
"exp": time.Now().Add(15 * time.Minute).Unix(),
|
||||
"iat": time.Now().Unix(),
|
||||
}
|
||||
|
||||
accessToken := jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims)
|
||||
signedAccessToken, err := accessToken.SignedString(accessTokenSecret)
|
||||
if err != nil {
|
||||
log.Println("Error generating access token:", err)
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
refreshClaims := jwt.MapClaims{
|
||||
"sub": user.ID,
|
||||
"exp": time.Now().Add(7 * 24 * time.Hour).Unix(),
|
||||
"iat": time.Now().Unix(),
|
||||
}
|
||||
|
||||
refreshToken := jwt.NewWithClaims(jwt.SigningMethodHS256, refreshClaims)
|
||||
signedRefreshToken, err := refreshToken.SignedString(refreshTokenSecret)
|
||||
if err != nil {
|
||||
log.Println("Error generating refresh token:", err)
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return signedAccessToken, signedRefreshToken, nil
|
||||
}
|
||||
|
||||
// ValidateJWT parses and validates the provided token string.
|
||||
// If isRefreshToken is true, the refresh secret is used; otherwise the access secret is used.
|
||||
func ValidateJWT(tokenString string, isRefreshToken bool) (jwt.MapClaims, error) {
|
||||
secret := accessTokenSecret
|
||||
if isRefreshToken {
|
||||
secret = refreshTokenSecret
|
||||
}
|
||||
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, jwt.NewValidationError("invalid signing method", jwt.ValidationErrorSignatureInvalid)
|
||||
}
|
||||
return secret, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
claims, ok := token.Claims.(jwt.MapClaims)
|
||||
if !ok || !token.Valid {
|
||||
return nil, errors.New("invalid token")
|
||||
}
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
// GenerateRandomToken returns a 128-bit random hex token (32 chars).
|
||||
func GenerateRandomToken() string {
|
||||
bytes := make([]byte, 16)
|
||||
_, err := rand.Read(bytes)
|
||||
if err != nil {
|
||||
panic("Failed to generate random token")
|
||||
}
|
||||
return hex.EncodeToString(bytes)
|
||||
}
|
||||
33
services/post-service/Dockerfile
Normal file
33
services/post-service/Dockerfile
Normal file
@@ -0,0 +1,33 @@
|
||||
FROM golang:1.24-alpine AS build
|
||||
RUN apk add --no-cache git ca-certificates protobuf-dev
|
||||
WORKDIR /app
|
||||
|
||||
# use repo-level go.mod so modules are resolvable
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
# install Go protoc plugins
|
||||
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@latest && \
|
||||
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
||||
|
||||
# copy service sources and proto files
|
||||
COPY services/post-service ./services/post-service
|
||||
COPY proto ./proto
|
||||
|
||||
# generate proto files
|
||||
RUN mkdir -p proto/auth proto/user proto/post && \
|
||||
export PATH=/go/bin:$PATH && \
|
||||
protoc --go_out=proto --go_opt=paths=source_relative \
|
||||
--go-grpc_out=proto --go-grpc_opt=paths=source_relative \
|
||||
--proto_path=proto \
|
||||
proto/auth.proto proto/user.proto proto/post.proto && \
|
||||
ls -la proto/auth/ proto/user/ proto/post/
|
||||
|
||||
# build static binary
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /usr/local/bin/post-service ./services/post-service/cmd
|
||||
|
||||
FROM alpine:3.20
|
||||
RUN apk add --no-cache ca-certificates
|
||||
COPY --from=build /usr/local/bin/post-service /usr/local/bin/post-service
|
||||
EXPOSE 50053
|
||||
ENTRYPOINT ["/usr/local/bin/post-service"]
|
||||
40
services/post-service/cmd/main.go
Normal file
40
services/post-service/cmd/main.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
pb "go-microservices/proto/post"
|
||||
"go-microservices/services/post-service/internal/database"
|
||||
"go-microservices/services/post-service/internal/repository"
|
||||
"go-microservices/services/post-service/internal/server"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("Starting Post Service...")
|
||||
port := os.Getenv("PORT")
|
||||
if port == "" {
|
||||
port = "50053"
|
||||
}
|
||||
lis, err := net.Listen("tcp", ":"+port)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to listen: %v", err)
|
||||
}
|
||||
|
||||
db, err := database.Init()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to init database: %v", err)
|
||||
}
|
||||
|
||||
repo := repository.NewRepository(db)
|
||||
|
||||
grpcServer := grpc.NewServer()
|
||||
pb.RegisterPostServiceServer(grpcServer, server.NewPostServer(repo))
|
||||
log.Printf("Post Service listening on %s", port)
|
||||
if err := grpcServer.Serve(lis); err != nil {
|
||||
log.Fatalf("Failed to serve: %v", err)
|
||||
}
|
||||
}
|
||||
1
services/post-service/config/config.yml
Normal file
1
services/post-service/config/config.yml
Normal file
@@ -0,0 +1 @@
|
||||
This is the configuration file for the Post Service microservice.
|
||||
22
services/post-service/config/env.go
Normal file
22
services/post-service/config/env.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Env struct {
|
||||
Port int
|
||||
JWTSecret string
|
||||
TokenDuration int
|
||||
DatabaseURL string
|
||||
RedisAddr string
|
||||
RedisPassword string
|
||||
RedisDB int
|
||||
EmailHost string
|
||||
EmailPort int
|
||||
EmailUsername string
|
||||
EmailPassword string
|
||||
EmailFrom string
|
||||
FrontendURL string
|
||||
}
|
||||
22
services/post-service/internal/database/database.go
Normal file
22
services/post-service/internal/database/database.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func Init() (*gorm.DB, error) {
|
||||
dsn := os.Getenv("DATABASE_URL")
|
||||
if dsn == "" {
|
||||
dsn = "host=localhost user=postgres password=postgres dbname=postgres port=5432 sslmode=disable TimeZone=UTC"
|
||||
}
|
||||
|
||||
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
7
services/post-service/internal/models/models.go
Normal file
7
services/post-service/internal/models/models.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package models
|
||||
|
||||
// TODO: Add database models for post service
|
||||
// Example models:
|
||||
// - Post model for blog posts
|
||||
// - Comment model for post comments
|
||||
// - Category model for post categorization
|
||||
7
services/post-service/internal/repository/repository.go
Normal file
7
services/post-service/internal/repository/repository.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package repository
|
||||
|
||||
// TODO: Add database repository implementations
|
||||
// Example repositories:
|
||||
// - PostRepository for post data access
|
||||
// - CommentRepository for comment management
|
||||
// - CategoryRepository for category management
|
||||
13
services/post-service/internal/repository/repository_impl.go
Normal file
13
services/post-service/internal/repository/repository_impl.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Repository struct {
|
||||
DB *gorm.DB
|
||||
}
|
||||
|
||||
func NewRepository(db *gorm.DB) *Repository {
|
||||
return &Repository{DB: db}
|
||||
}
|
||||
47
services/post-service/internal/server/server.go
Normal file
47
services/post-service/internal/server/server.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
pb "go-microservices/proto/post"
|
||||
"go-microservices/services/post-service/internal/repository"
|
||||
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
type PostServer struct {
|
||||
pb.UnimplementedPostServiceServer
|
||||
repo *repository.Repository
|
||||
}
|
||||
|
||||
func NewPostServer(repo *repository.Repository) *PostServer {
|
||||
return &PostServer{repo: repo}
|
||||
}
|
||||
|
||||
func (s *PostServer) CreatePost(ctx context.Context, req *pb.CreatePostRequest) (*pb.CreatePostResponse, error) {
|
||||
// TODO: Implement create post logic
|
||||
post := &pb.Post{Id: "1", AuthorId: req.AuthorId, Title: req.Title, Content: req.Content, CreatedAt: 0, UpdatedAt: 0}
|
||||
return &pb.CreatePostResponse{Post: post}, nil
|
||||
}
|
||||
|
||||
func (s *PostServer) GetPost(ctx context.Context, req *pb.GetPostRequest) (*pb.GetPostResponse, error) {
|
||||
// TODO: Implement get post logic
|
||||
post := &pb.Post{Id: req.Id, AuthorId: "author", Title: "title", Content: "content", CreatedAt: 0, UpdatedAt: 0}
|
||||
return &pb.GetPostResponse{Post: post}, nil
|
||||
}
|
||||
|
||||
func (s *PostServer) UpdatePost(ctx context.Context, req *pb.UpdatePostRequest) (*pb.UpdatePostResponse, error) {
|
||||
// TODO: Implement update post logic
|
||||
post := &pb.Post{Id: req.Id, AuthorId: "author", Title: req.Title, Content: req.Content, CreatedAt: 0, UpdatedAt: 0}
|
||||
return &pb.UpdatePostResponse{Post: post}, nil
|
||||
}
|
||||
|
||||
func (s *PostServer) DeletePost(ctx context.Context, req *pb.DeletePostRequest) (*emptypb.Empty, error) {
|
||||
// TODO: Implement delete post logic
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *PostServer) ListPosts(ctx context.Context, req *pb.ListPostsRequest) (*pb.ListPostsResponse, error) {
|
||||
// TODO: Implement list posts logic
|
||||
posts := []*pb.Post{}
|
||||
return &pb.ListPostsResponse{Posts: posts}, nil
|
||||
}
|
||||
33
services/user-service/Dockerfile
Normal file
33
services/user-service/Dockerfile
Normal file
@@ -0,0 +1,33 @@
|
||||
FROM golang:1.24-alpine AS build
|
||||
RUN apk add --no-cache git ca-certificates protobuf-dev
|
||||
WORKDIR /app
|
||||
|
||||
# use repo-level go.mod so modules are resolvable
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
# install Go protoc plugins
|
||||
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@latest && \
|
||||
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
||||
|
||||
# copy service sources and proto files
|
||||
COPY services/user-service ./services/user-service
|
||||
COPY proto ./proto
|
||||
|
||||
# generate proto files
|
||||
RUN mkdir -p proto/auth proto/user proto/post && \
|
||||
export PATH=/go/bin:$PATH && \
|
||||
protoc --go_out=proto --go_opt=paths=source_relative \
|
||||
--go-grpc_out=proto --go-grpc_opt=paths=source_relative \
|
||||
--proto_path=proto \
|
||||
proto/auth.proto proto/user.proto proto/post.proto && \
|
||||
ls -la proto/auth/ proto/user/ proto/post/
|
||||
|
||||
# build static binary
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /usr/local/bin/user-service ./services/user-service/cmd
|
||||
|
||||
FROM alpine:3.20
|
||||
RUN apk add --no-cache ca-certificates
|
||||
COPY --from=build /usr/local/bin/user-service /usr/local/bin/user-service
|
||||
EXPOSE 50052
|
||||
ENTRYPOINT ["/usr/local/bin/user-service"]
|
||||
39
services/user-service/cmd/main.go
Normal file
39
services/user-service/cmd/main.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
pb "go-microservices/proto/user"
|
||||
"go-microservices/services/user-service/internal/database"
|
||||
"go-microservices/services/user-service/internal/repository"
|
||||
"go-microservices/services/user-service/internal/server"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("Starting User Service...")
|
||||
port := os.Getenv("PORT")
|
||||
if port == "" {
|
||||
port = "50052"
|
||||
}
|
||||
lis, err := net.Listen("tcp", ":"+port)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to listen: %v", err)
|
||||
}
|
||||
db, err := database.Init()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to init database: %v", err)
|
||||
}
|
||||
|
||||
repo := repository.NewRepository(db)
|
||||
|
||||
grpcServer := grpc.NewServer()
|
||||
pb.RegisterUserServiceServer(grpcServer, server.NewUserServer(repo))
|
||||
log.Printf("User Service listening on %s", port)
|
||||
if err := grpcServer.Serve(lis); err != nil {
|
||||
log.Fatalf("Failed to serve: %v", err)
|
||||
}
|
||||
}
|
||||
1
services/user-service/config/config.yml
Normal file
1
services/user-service/config/config.yml
Normal file
@@ -0,0 +1 @@
|
||||
This is the configuration file for the User Service microservice. It defines the settings and parameters used by the service at runtime.
|
||||
22
services/user-service/config/env.go
Normal file
22
services/user-service/config/env.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Env struct {
|
||||
Port int
|
||||
JWTSecret string
|
||||
TokenDuration int
|
||||
DatabaseURL string
|
||||
RedisAddr string
|
||||
RedisPassword string
|
||||
RedisDB int
|
||||
EmailHost string
|
||||
EmailPort int
|
||||
EmailUsername string
|
||||
EmailPassword string
|
||||
EmailFrom string
|
||||
FrontendURL string
|
||||
}
|
||||
28
services/user-service/internal/database/database.go
Normal file
28
services/user-service/internal/database/database.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"go-microservices/services/user-service/internal/models"
|
||||
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func Init() (*gorm.DB, error) {
|
||||
dsn := os.Getenv("DATABASE_URL")
|
||||
if dsn == "" {
|
||||
dsn = "host=localhost user=postgres password=postgres dbname=postgres port=5432 sslmode=disable TimeZone=UTC"
|
||||
}
|
||||
|
||||
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := db.AutoMigrate(&models.User{}, &models.Client{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
27
services/user-service/internal/models/models.go
Normal file
27
services/user-service/internal/models/models.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
gorm.Model
|
||||
Name string
|
||||
Email string `gorm:"uniqueIndex;not null"`
|
||||
Active bool `gorm:"default:true"`
|
||||
Address string
|
||||
}
|
||||
|
||||
type User struct {
|
||||
gorm.Model
|
||||
Email string `gorm:"uniqueIndex;not null"`
|
||||
Name string
|
||||
Role string `gorm:"default:User"`
|
||||
Active bool `gorm:"default:true"`
|
||||
Username string `gorm:"uniqueIndex"`
|
||||
Address string
|
||||
PhoneNumber string
|
||||
ProfilePhoto []byte `gorm:"type:bytea"`
|
||||
ClientID *uint
|
||||
Client *Client
|
||||
}
|
||||
7
services/user-service/internal/repository/repository.go
Normal file
7
services/user-service/internal/repository/repository.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package repository
|
||||
|
||||
// TODO: Add database repository implementations
|
||||
// Example repositories:
|
||||
// - UserRepository for user data access
|
||||
// - ProfileRepository for profile management
|
||||
// - PreferenceRepository for user settings
|
||||
13
services/user-service/internal/repository/repository_impl.go
Normal file
13
services/user-service/internal/repository/repository_impl.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Repository struct {
|
||||
DB *gorm.DB
|
||||
}
|
||||
|
||||
func NewRepository(db *gorm.DB) *Repository {
|
||||
return &Repository{DB: db}
|
||||
}
|
||||
49
services/user-service/internal/server/server.go
Normal file
49
services/user-service/internal/server/server.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
pb "go-microservices/proto/user"
|
||||
|
||||
"go-microservices/services/user-service/internal/repository"
|
||||
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
type UserServer struct {
|
||||
pb.UnimplementedUserServiceServer
|
||||
repo *repository.Repository
|
||||
}
|
||||
|
||||
func NewUserServer(repo *repository.Repository) *UserServer {
|
||||
return &UserServer{repo: repo}
|
||||
}
|
||||
|
||||
func (s *UserServer) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.CreateUserResponse, error) {
|
||||
// TODO: Implement create user logic
|
||||
user := &pb.User{Id: "1", Username: req.Username, Email: req.Email, Bio: req.Bio, AvatarUrl: req.AvatarUrl, CreatedAt: 0, UpdatedAt: 0}
|
||||
return &pb.CreateUserResponse{User: user}, nil
|
||||
}
|
||||
|
||||
func (s *UserServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
|
||||
// TODO: Implement get user logic
|
||||
user := &pb.User{Id: req.Id, Username: "user", Email: "user@example.com", Bio: "bio", AvatarUrl: "", CreatedAt: 0, UpdatedAt: 0}
|
||||
return &pb.GetUserResponse{User: user}, nil
|
||||
}
|
||||
|
||||
func (s *UserServer) UpdateUser(ctx context.Context, req *pb.UpdateUserRequest) (*pb.UpdateUserResponse, error) {
|
||||
// TODO: Implement update user logic
|
||||
user := &pb.User{Id: req.Id, Username: req.Username, Email: req.Email, Bio: req.Bio, AvatarUrl: req.AvatarUrl, CreatedAt: 0, UpdatedAt: 0}
|
||||
return &pb.UpdateUserResponse{User: user}, nil
|
||||
}
|
||||
|
||||
func (s *UserServer) DeleteUser(ctx context.Context, req *pb.DeleteUserRequest) (*emptypb.Empty, error) {
|
||||
// TODO: Implement delete user logic
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *UserServer) ListUsers(ctx context.Context, req *pb.ListUsersRequest) (*pb.ListUsersResponse, error) {
|
||||
// TODO: Implement list users logic
|
||||
users := []*pb.User{}
|
||||
return &pb.ListUsersResponse{Users: users}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user