Skip to content
Merged
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
49 changes: 49 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Progress Wall — AI agent instructions (concise)

This file is a short, actionable guide for AI coding agents working on this repo. Focus on the files and patterns below — they are the fastest way to be productive.

Backend (Go, Clean-ish architecture)
- Key dirs: `backend/models/`, `backend/dto/`, `backend/repository/`, `backend/services/`, `backend/router/`.
- Important files: `backend/main.go`, `backend/config/config.go` (uses `godotenv`), `backend/database/database.go` (uses GORM; supports `sqlite` and `mysql`).
- Routing: `backend/router/Router.go` registers routes under `/api/*`. Example: `userGroup.POST("/register", userhandler.Register)` and `UserHandler.Register` binds `dto.RegisterInput` via `c.ShouldBindJSON`.

Frontend (Vue 3 + TypeScript)
- Key dirs: `frontend/src/components/` (`features/`, `ui/`), `frontend/src/stores/` (Pinia), `frontend/src/lib/` (API helpers), `frontend/src/views/`.
- `frontend/src/lib/api.ts` creates an Axios instance with `baseURL` from `VITE_API_BASE_URL || '/api'`, attaches `Authorization: Bearer <token>` using `useUserStore().getToken()` and logs out on 401. Ensure the user store exposes `getToken()` or adapt `api.ts`.

Concrete patterns to follow (do not invent):
- DTO-first for handlers: handlers call `ShouldBindJSON` into `backend/dto/*.go` types. See `backend/router/UserHandler.go`.
- Business logic in services: handlers call `services.*` (example: `userService.Register(username, email, password)`).
- DB access in repository layer; services orchestrate repository calls.
- Config from env via `backend/config/config.go` — default DB type is `sqlite` (file `progress_wall.db`), or `mysql` if configured.

Developer workflows / commands (Windows PowerShell)
```powershell
# backend
Set-Location backend; cp config.env.example config.env; go run main.go

# frontend
Set-Location frontend; pnpm install; pnpm dev

# optional: docker compose
docker-compose up --build
```

Integration notes & gotchas discovered in code
- API base path: frontend expects API under `/api` (proxy or same-origin). Router registers `/api/users` in backend.
- `api.ts` assumes `useUserStore().getToken()` exists — current `frontend/src/stores/user.ts` in tree provides `logout()` but no `getToken()`; update either the store or `api.ts` to avoid runtime errors.
- Database: `backend/database/database.go` uses a singleton pattern (`once.Do`) and will fatal if `GetDB()` called before `InitDB()`.

When adding endpoints
1. Add request/response types in `backend/dto/` (name types clearly, e.g. `RegisterInput`).
2. Add handler in `backend/router/*` and register route in `Router.go` under the appropriate group (e.g., `/api/users`).
3. Implement service method in `backend/services/` and repository logic in `backend/repository/` if DB access is needed.

Files that are the fastest to open for context
- `backend/router/UserHandler.go` (handler patterns and error responses)
- `backend/config/config.go` (env keys and defaults)
- `backend/database/database.go` (DB initialization and supported drivers)
- `frontend/src/lib/api.ts` (axios, auth header, 401 behavior)
- `frontend/src/stores/user.ts` (local user state; may need token methods)

If anything in this file is unclear or you want more examples (sample DTOs, service signatures, or common test patterns), tell me which area to expand and I will update this doc.
19 changes: 19 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Node.js dependencies
node_modules/

# Build outputs
dist/
build/

# Logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

# Editor files
.vscode/
.DS_Store

# Environment files
.env
19 changes: 19 additions & 0 deletions backend/config.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# 数据库配置,测试时不想装mysql的话可选sqlite
DB_TYPE=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_NAME=progress_wall
DB_USER=root
DB_PASSWORD=123456


# 服务器配置
SERVER_PORT=8080
SERVER_MODE=debug

# JWT配置
JWT_SECRET=kfcvme50
JWT_EXPIRE_HOURS=24

# CORS配置
CORS_ALLOW_ORIGINS=http://localhost:3000,http://localhost:5173
6 changes: 5 additions & 1 deletion backend/config/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package config

import (
"fmt"
"os"
"strconv"

Expand Down Expand Up @@ -38,6 +39,9 @@ type CORSConfig struct {
}

func Load() *Config {
if err := godotenv.Load("config.env"); err != nil {
fmt.Println("Warning: config.env not found, using system env")
}
// 加载环境变量文件
_ = godotenv.Load()

Expand All @@ -47,7 +51,7 @@ func Load() *Config {
Mode: getEnv("SERVER_MODE", "debug"),
},
DB: DatabaseConfig{
Type: getEnv("DB_TYPE", "sqlite"),
Type: getEnv("DB_TYPE", "mysql"),
Host: getEnv("DB_HOST", "localhost"),
Port: getEnv("DB_PORT", "3306"),
Name: getEnv("DB_NAME", "progress_wall"),
Expand Down
5 changes: 5 additions & 0 deletions backend/database/modernc_sqlite.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package database

// Import the pure-Go modernc sqlite driver to avoid cgo dependency (replaces mattn/go-sqlite3).
// The driver registers itself with database/sql on import.
import _ "modernc.org/sqlite"
6 changes: 6 additions & 0 deletions backend/dto/LoginInput.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package dto

type LoginInput struct {
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required,min=6"`
}
7 changes: 7 additions & 0 deletions backend/dto/RegisterInput.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package dto

type RegisterInput struct {
Username string `json:"username" binding:"required,min=3,max=20"`
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required,min=6,max=50"`
}
44 changes: 34 additions & 10 deletions backend/main.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,49 @@
package main

import (
"fmt"
"log"
"progress-wall-backend/config"
"progress-wall-backend/database"
"progress-wall-backend/routes"
)

func main() {
// 加载配置

cfg := config.Load()

// 打印配置信息
fmt.Printf("服务器配置:\n")
fmt.Printf("- 端口: %s\n", cfg.Server.Port)
fmt.Printf("- 模式: %s\n", cfg.Server.Mode)
fmt.Printf("- 数据库类型: %s\n", cfg.DB.Type)
fmt.Printf("- 数据库名称: %s\n", cfg.DB.Name)
fmt.Printf("- JWT密钥长度: %d\n", len(cfg.JWT.Secret))
fmt.Printf("- CORS允许来源: %s\n", cfg.CORS.AllowOrigins)
// 打印数据库配置,检查用户名/密码是否正确
fmt.Printf("DB_USER=%s, DB_PASSWORD=%s, DB_HOST=%s\n", cfg.DB.User, cfg.DB.Password, cfg.DB.Host)

// 下面是你原来的 InitDB 调用
if err := database.InitDB(cfg); err != nil {
log.Fatalf("Failed to initialize database: %v", err)
}

// 初始化数据库
if err := database.InitDB(cfg); err != nil {
log.Fatalf("Failed to initialize database: %v", err)
}

// 获取数据库实例
db := database.GetDB()

// 自动迁移数据库表
if err := db.AutoMigrate(&models.User{}); err != nil {
log.Fatalf("Failed to migrate database: %v", err)
}

// 设置Gin模式
gin.SetMode(cfg.Server.Mode)

// 初始化依赖注入层
userRepo := repository.NewUserRepository(db)
userService := services.NewUserService(userRepo, cfg)

// 初始化路由
deps := router.HandlerDependencies{
UserService: userService,
}
r := router.NewRouter(deps)

// 初始化数据库
db, err := database.InitDB(cfg)
Expand Down
30 changes: 30 additions & 0 deletions backend/models/User.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package models

import (
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
)

type User struct {
gorm.Model
Username string `gorm:"uniqueIndex;size:255;not null"`
Email string `gorm:"uniqueIndex;size:255;not null"`
Password string `gorm:"size:255;not null"`
}

// SetPassword 加密密码
func (user *User) SetPassword(password string) error {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return err
}
user.Password = string(hashedPassword)
return nil
}

// checkPassword 检查密码
func (user *User) CheckPassword(password string) bool {
err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
//如果err = nil 说明密码匹配
return err == nil
}
44 changes: 44 additions & 0 deletions backend/repository/UserRepository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package repository

import (
"progress-wall-backend/models"

"gorm.io/gorm"
)

type UserRepository interface {
Save(user *models.User) error
FindByUsername(username string) (*models.User, error)
FindByEmail(email string) (*models.User, error)
}

type userRepository struct {
db *gorm.DB
}

func NewUserRepository(db *gorm.DB) UserRepository {
return &userRepository{db: db}
}

// 保存用户
func (r *userRepository) Save(user *models.User) (error) {
return r.db.Create(user).Error
}

// 根据用户名查找用户
func (r *userRepository) FindByUsername(username string) (*models.User, error) {
var user models.User
if err := r.db.Where("username = ?", username).First(&user).Error; err != nil {
return nil, err
}
return &user, nil
}

// 根据邮箱查找用户
func (r *userRepository) FindByEmail(email string) (*models.User, error) {
var user models.User
if err := r.db.Where("email = ?", email).First(&user).Error; err != nil {
return nil, err
}
return &user, nil
}
35 changes: 35 additions & 0 deletions backend/router/Router.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package router

import (
"progress-wall-backend/services"

"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)

type HandlerDependencies struct {
UserService services.UserService
}

func NewRouter(deps HandlerDependencies) *gin.Engine {
router := gin.Default()

// CORS 配置
router.Use(cors.New(cors.Config{
AllowOrigins: []string{"*"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
}))

// 用户相关路由组
userHandler := NewUserHandler(deps.UserService)
userGroup := router.Group("/api/auth")
{
userGroup.POST("/register", userHandler.Register) // 注册
userGroup.POST("/login", userHandler.Login) // 登录
}

return router
}
54 changes: 54 additions & 0 deletions backend/router/UserHandler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package router

import (
"net/http"
"progress-wall-backend/dto"
"progress-wall-backend/services"

"github.com/gin-gonic/gin"
)

type UserHandler struct {
userService services.UserService
}

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

// Register 用户注册
func (h *UserHandler) Register(c *gin.Context) {
var input dto.RegisterInput
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

user, err := h.userService.Register(input.Username, input.Email, input.Password)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"user": user})
}

// Login 用户登录
func (h *UserHandler) Login(c *gin.Context) {
var input dto.LoginInput
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

token, user, err := h.userService.Login(input.Email, input.Password)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
return
}

c.JSON(http.StatusOK, gin.H{
"token": token,
"user": user,
})

}
Loading