Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
a6bef10
feat: login page desain
Arjuna-Ragil Jan 20, 2026
df8e21e
Merge pull request #10 from Arjuna-Ragil/feat/3-login-design
Arjuna-Ragil Jan 20, 2026
585c5d7
feat: user repo, service, handler, & route added
Arjuna-Ragil Jan 21, 2026
018d205
Merge pull request #11 from Arjuna-Ragil/feat/4-user-services
Arjuna-Ragil Jan 21, 2026
c1101c1
chore: CI workflow
Arjuna-Ragil Jan 21, 2026
40459f8
Merge pull request #13 from Arjuna-Ragil/chore/12-cicd-workflow
Arjuna-Ragil Jan 21, 2026
85e00c0
feat: register system added
Arjuna-Ragil Jan 21, 2026
7855756
feat: Login and jwt added
Arjuna-Ragil Jan 21, 2026
e3d56de
Merge pull request #14 from Arjuna-Ragil/feat/5-login-system
Arjuna-Ragil Jan 21, 2026
636f64c
feat: admin login logic added
Arjuna-Ragil Jan 21, 2026
bc2744c
Merge pull request #15 from Arjuna-Ragil/feat/6-Admin-login
Arjuna-Ragil Jan 21, 2026
0250897
feat: Home page desain added
Arjuna-Ragil Jan 21, 2026
cdbea93
Merge pull request #17 from Arjuna-Ragil/feat/16-Home-Design
Arjuna-Ragil Jan 21, 2026
1e505f5
feat: auth middleware added
Arjuna-Ragil Jan 21, 2026
02b39d9
Merge pull request #18 from Arjuna-Ragil/feat/7-auth-middleware
Arjuna-Ragil Jan 21, 2026
d454120
bug: ci workflow fix
Arjuna-Ragil Jan 21, 2026
6a591ca
bug: ci workflow fix #2
Arjuna-Ragil Jan 21, 2026
8ffc2cb
bug: ci workflow fix #3
Arjuna-Ragil Jan 21, 2026
42716f7
fix: ci workflow fix #4
Arjuna-Ragil Jan 21, 2026
d95c89a
fix: ci workflow #5
Arjuna-Ragil Jan 21, 2026
ef66573
fix: ci workflow #6
Arjuna-Ragil Jan 21, 2026
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
63 changes: 63 additions & 0 deletions .github/workflows/CI-checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: CI integrity checks

on:
pull_request:
branches:
- main
- dev
push:
branches:
- main

jobs:
backend-checks:
name: Backend integrity checks
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: '1.25'
cache: true

- name: Download Dependencies
run: go mod download

- name: Verify Backend Build
run: go build -v ./...

- name: Run Tests
run: go test -v ./...

frontend-checks:
name: Frontend integrity checks
runs-on: ubuntu-latest
defaults:
run:
working-directory: lb-web
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: Lb-web/package-lock.json

- name: Install Dependencies
run: npm ci

- name: Type check & lint
run: npm run lint
continue-on-error: true

- name: Run Tests
run: npm run test

- name: Build
run: npm run build
26 changes: 26 additions & 0 deletions Internal/adapters/repository/authRepo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package repository

import "github.com/Arjuna-Ragil/Localbase/Internal/core/domain"

type AuthRepo struct {
DB *DBContainer
}

func NewAuthRepo(db *DBContainer) *AuthRepo {
return &AuthRepo{DB: db}
}

func (ar *AuthRepo) RegisterRepo(user *domain.User) error {
if err := ar.DB.Gorm.Create(user).Error; err != nil {
return err
}
return nil
}

func (ar *AuthRepo) LoginRepo(email string) (*domain.User, error) {
var user domain.User
if err := ar.DB.Gorm.Where("email = ?", email).First(&user).Error; err != nil {
return nil, err
}
return &user, nil
}
49 changes: 49 additions & 0 deletions Internal/adapters/repository/postgres.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package repository

import (
"fmt"
"log"

"github.com/Arjuna-Ragil/Localbase/Internal/config"
"github.com/Arjuna-Ragil/Localbase/Internal/core/domain"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)

type DBContainer struct {
Gorm *gorm.DB
Sqlx *sqlx.DB
}

func ConnectDB() (*DBContainer, error) {
cfg := config.LoadConfig()

gormDB, err := gorm.Open(postgres.Open(cfg.DBURL), &gorm.Config{})
if err != nil {
log.Fatalf("Failed to connect to gorm database: %v", err)
}
fmt.Println("Connected to gorm database")

sqlxDB, err := sqlx.Connect("postgres", cfg.DBURL)
if err != nil {
log.Fatalf("Failed to connect to sqlx database: %v", err)
}
fmt.Println("Connected to sqlx database")

return &DBContainer{
Gorm: gormDB,
Sqlx: sqlxDB,
}, nil
}
func (db *DBContainer) Migrate() error {
err := db.Gorm.AutoMigrate(
&domain.User{},
)
if err != nil {
log.Fatalf("Failed to migrate users: %v", err)
}
fmt.Println("Migrated users")
return nil
}
19 changes: 19 additions & 0 deletions Internal/adapters/repository/sysRepo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package repository

import "github.com/Arjuna-Ragil/Localbase/Internal/core/domain"

type SystemRepo struct {
DB *DBContainer
}

func NewSystemRepo(db *DBContainer) *SystemRepo {
return &SystemRepo{DB: db}
}

func (sr *SystemRepo) IsAppInitialized() (bool, error) {
var count int64
if err := sr.DB.Gorm.Model(&domain.User{}).Count(&count).Error; err != nil {
return false, err
}
return count > 0, nil
}
30 changes: 30 additions & 0 deletions Internal/adapters/repository/userRepo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package repository

import "github.com/Arjuna-Ragil/Localbase/Internal/core/domain"

type UserQuery interface {
Save(user *domain.User) error
FindById(id string) (*domain.User, error)
}
type UserRepository struct {
db *DBContainer
}

func NewUserRepository(db *DBContainer) *UserRepository {
return &UserRepository{db: db}
}

// Query

func (ur *UserRepository) save(user *domain.User) error {
return ur.db.Gorm.Create(user).Error
}

func (ur *UserRepository) FindById(id uint) (*domain.User, error) {
var user domain.User
err := ur.db.Gorm.First(&user, id).Error
if err != nil {
return nil, err
}
return &user, nil
}
45 changes: 45 additions & 0 deletions Internal/api/Routes/routev1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package Routes

import (
"github.com/Arjuna-Ragil/Localbase/Internal/adapters/repository"
"github.com/Arjuna-Ragil/Localbase/Internal/api/handlers"
"github.com/Arjuna-Ragil/Localbase/Internal/api/middleware"
"github.com/Arjuna-Ragil/Localbase/Internal/config"
"github.com/gin-gonic/gin"
)

type Deps struct {
User *handlers.UserHandler
Auth *handlers.AuthHandler
System *handlers.SystemHandler
UserRepo *repository.UserRepository
Config *config.Config
}

func SetupRouterV1(r *gin.Engine, deps Deps) {
api := r.Group("/api/v1")

authMiddleware := middleware.AuthMiddleware(deps.UserRepo, deps.Config)
{
system := api.Group("/system")
{
system.GET("/status", deps.System.CheckAppInitStatus)
}

auth := api.Group("/auth")
{
auth.POST("/Register", deps.Auth.RegisterHandler)
auth.POST("/Login", deps.Auth.LoginHandler)
auth.POST("/logout", deps.Auth.LogoutHandler)
}

protected := api.Group("/protected")
protected.Use(authMiddleware)
{
user := protected.Group("/user")
{
user.GET("/me", deps.User.GetUserHandler)
}
}
}
}
71 changes: 71 additions & 0 deletions Internal/api/handlers/authHandler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package handlers

import (
"net/http"

"github.com/Arjuna-Ragil/Localbase/Internal/core/services"
"github.com/gin-gonic/gin"
)

type AuthHandler struct {
Service *services.AuthService
}

func NewAuthHandler(service *services.AuthService) *AuthHandler {
return &AuthHandler{Service: service}
}

func (ah *AuthHandler) RegisterHandler(c *gin.Context) {
var input services.RegisterInput
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"message": "Invalid Input",
"data": err.Error(),
})
return
}
if err := ah.Service.RegisterService(&input); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"message": "Register service failed",
"data": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "Register service success",
"data": nil,
})
}

func (ah *AuthHandler) LoginHandler(c *gin.Context) {
var input services.LoginInput
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"message": "Invalid Input",
"data": err.Error(),
})
return
}
tokenString, err := ah.Service.LoginService(&input)
if err != nil {
c.JSON(401, gin.H{
"message": "Login failed",
"data": err.Error(),
})
return
}
c.SetSameSite(http.SameSiteLaxMode)
c.SetCookie("authorization", tokenString, 3600*24, "/", "", false, true)
c.JSON(http.StatusOK, gin.H{
"message": "Login success",
"data": tokenString,
})
}

func (ah *AuthHandler) LogoutHandler(c *gin.Context) {
c.SetCookie("authorization", "", -1, "/", "", false, true)
c.JSON(http.StatusOK, gin.H{
"message": "Logout success",
"data": nil,
})
}
24 changes: 24 additions & 0 deletions Internal/api/handlers/sysHandler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package handlers

import (
"net/http"

"github.com/Arjuna-Ragil/Localbase/Internal/core/services"
"github.com/gin-gonic/gin"
)

type SystemHandler struct {
Service *services.SystemService
}

func NewSystemHandler(service *services.SystemService) *SystemHandler {
return &SystemHandler{Service: service}
}

func (sh *SystemHandler) CheckAppInitStatus(c *gin.Context) {
isInit, _ := sh.Service.CheckAppInitialized()
c.JSON(http.StatusOK, gin.H{
"message": "App init status",
"data": isInit,
})
}
37 changes: 37 additions & 0 deletions Internal/api/handlers/userHandler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package handlers

import (
"github.com/Arjuna-Ragil/Localbase/Internal/core/services"
"github.com/gin-gonic/gin"
)

type UserHandler struct {
Service *services.UserService
}

func NewUserHandler(s *services.UserService) *UserHandler {
return &UserHandler{Service: s}
}

func (uh *UserHandler) GetUserHandler(c *gin.Context) {
userID, exist := c.Get("userID")
if !exist {
c.JSON(400, gin.H{
"message": "Unable to find user",
"data": exist,
})
return
}
user, err := uh.Service.GetUser(userID.(uint))
if err != nil {
c.JSON(400, gin.H{
"message": "Unable to get user data",
"data": err,
})
return
}
c.JSON(200, gin.H{
"message": "Successfully get user data",
"data": user,
})
}
Loading
Loading