Chore: project init

This commit is contained in:
2026-02-24 13:18:43 +09:00
commit 3345549051
23 changed files with 788 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
package announcement
import "github.com/gofiber/fiber/v2"
type Handler struct {
svc *Service
}
func NewHandler(svc *Service) *Handler {
return &Handler{svc: svc}
}
func (h *Handler) GetAll(c *fiber.Ctx) error {
list, err := h.svc.GetAll()
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "공지사항을 불러오지 못했습니다"})
}
return c.JSON(list)
}
func (h *Handler) Create(c *fiber.Ctx) error {
var body struct {
Title string `json:"title"`
Content string `json:"content"`
}
if err := c.BodyParser(&body); err != nil || body.Title == "" || body.Content == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "제목과 내용을 입력해주세요"})
}
a, err := h.svc.Create(body.Title, body.Content)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "공지사항 생성에 실패했습니다"})
}
return c.Status(fiber.StatusCreated).JSON(a)
}
func (h *Handler) Update(c *fiber.Ctx) error {
var body struct {
Title string `json:"title"`
Content string `json:"content"`
}
if err := c.BodyParser(&body); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "잘못된 요청입니다"})
}
a, err := h.svc.Update(c.Params("id"), body.Title, body.Content)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(a)
}
func (h *Handler) Delete(c *fiber.Ctx) error {
if err := h.svc.Delete(c.Params("id")); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "삭제에 실패했습니다"})
}
return c.SendStatus(fiber.StatusNoContent)
}

View File

@@ -0,0 +1,9 @@
package announcement
import "gorm.io/gorm"
type Announcement struct {
gorm.Model
Title string `gorm:"not null"`
Content string `gorm:"type:text;not null"`
}

View File

@@ -0,0 +1,35 @@
package announcement
import "gorm.io/gorm"
type Repository struct {
db *gorm.DB
}
func NewRepository(db *gorm.DB) *Repository {
return &Repository{db: db}
}
func (r *Repository) FindAll() ([]Announcement, error) {
var list []Announcement
err := r.db.Order("created_at desc").Find(&list).Error
return list, err
}
func (r *Repository) FindByID(id string) (*Announcement, error) {
var a Announcement
err := r.db.First(&a, id).Error
return &a, err
}
func (r *Repository) Create(a *Announcement) error {
return r.db.Create(a).Error
}
func (r *Repository) Save(a *Announcement) error {
return r.db.Save(a).Error
}
func (r *Repository) Delete(id string) error {
return r.db.Delete(&Announcement{}, id).Error
}

View File

@@ -0,0 +1,38 @@
package announcement
import "fmt"
type Service struct {
repo *Repository
}
func NewService(repo *Repository) *Service {
return &Service{repo: repo}
}
func (s *Service) GetAll() ([]Announcement, error) {
return s.repo.FindAll()
}
func (s *Service) Create(title, content string) (*Announcement, error) {
a := &Announcement{Title: title, Content: content}
return a, s.repo.Create(a)
}
func (s *Service) Update(id, title, content string) (*Announcement, error) {
a, err := s.repo.FindByID(id)
if err != nil {
return nil, fmt.Errorf("공지사항을 찾을 수 없습니다")
}
if title != "" {
a.Title = title
}
if content != "" {
a.Content = content
}
return a, s.repo.Save(a)
}
func (s *Service) Delete(id string) error {
return s.repo.Delete(id)
}

41
internal/auth/handler.go Normal file
View File

@@ -0,0 +1,41 @@
package auth
import "github.com/gofiber/fiber/v2"
type Handler struct {
svc *Service
}
func NewHandler(svc *Service) *Handler {
return &Handler{svc: svc}
}
func (h *Handler) Login(c *fiber.Ctx) error {
var req struct {
Username string `json:"username"`
Password string `json:"password"`
}
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "잘못된 요청입니다"})
}
if req.Username == "" || req.Password == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "아이디와 비밀번호를 입력해주세요"})
}
tokenStr, user, err := h.svc.Login(req.Username, req.Password)
if err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{
"token": tokenStr,
"username": user.Username,
"role": user.Role,
})
}
func (h *Handler) Logout(c *fiber.Ctx) error {
userID := c.Locals("userID").(uint)
h.svc.Logout(userID)
return c.JSON(fiber.Map{"message": "로그아웃 되었습니다"})
}

17
internal/auth/model.go Normal file
View File

@@ -0,0 +1,17 @@
package auth
import "gorm.io/gorm"
type Role string
const (
RoleAdmin Role = "admin"
RoleUser Role = "user"
)
type User struct {
gorm.Model
Username string `gorm:"uniqueIndex;not null"`
PasswordHash string `gorm:"not null"`
Role Role `gorm:"default:'user'"`
}

View File

@@ -0,0 +1,21 @@
package auth
import "gorm.io/gorm"
type Repository struct {
db *gorm.DB
}
func NewRepository(db *gorm.DB) *Repository {
return &Repository{db: db}
}
func (r *Repository) FindByUsername(username string) (*User, error) {
var user User
err := r.db.Where("username = ?", username).First(&user).Error
return &user, err
}
func (r *Repository) Create(user *User) error {
return r.db.Create(user).Error
}

66
internal/auth/service.go Normal file
View File

@@ -0,0 +1,66 @@
package auth
import (
"context"
"fmt"
"time"
"a301_server/pkg/config"
"github.com/golang-jwt/jwt/v5"
"github.com/redis/go-redis/v9"
"golang.org/x/crypto/bcrypt"
)
type Claims struct {
UserID uint `json:"user_id"`
Username string `json:"username"`
Role string `json:"role"`
jwt.RegisteredClaims
}
type Service struct {
repo *Repository
rdb *redis.Client
}
func NewService(repo *Repository, rdb *redis.Client) *Service {
return &Service{repo: repo, rdb: rdb}
}
func (s *Service) Login(username, password string) (string, *User, error) {
user, err := s.repo.FindByUsername(username)
if err != nil {
return "", nil, fmt.Errorf("아이디 또는 비밀번호가 올바르지 않습니다")
}
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)); err != nil {
return "", nil, fmt.Errorf("아이디 또는 비밀번호가 올바르지 않습니다")
}
expiry := time.Duration(config.C.JWTExpiryHours) * time.Hour
claims := &Claims{
UserID: user.ID,
Username: user.Username,
Role: string(user.Role),
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(expiry)),
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenStr, err := token.SignedString([]byte(config.C.JWTSecret))
if err != nil {
return "", nil, fmt.Errorf("토큰 생성에 실패했습니다")
}
// Redis에 세션 저장 (1계정 1세션)
key := fmt.Sprintf("session:%d", user.ID)
s.rdb.Set(context.Background(), key, tokenStr, expiry)
return tokenStr, user, nil
}
func (s *Service) Logout(userID uint) {
key := fmt.Sprintf("session:%d", userID)
s.rdb.Del(context.Background(), key)
}

View File

@@ -0,0 +1,36 @@
package download
import "github.com/gofiber/fiber/v2"
type Handler struct {
svc *Service
}
func NewHandler(svc *Service) *Handler {
return &Handler{svc: svc}
}
func (h *Handler) GetInfo(c *fiber.Ctx) error {
info, err := h.svc.GetInfo()
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "다운로드 정보가 없습니다"})
}
return c.JSON(info)
}
func (h *Handler) Upsert(c *fiber.Ctx) error {
var body struct {
URL string `json:"url"`
Version string `json:"version"`
FileName string `json:"fileName"`
FileSize string `json:"fileSize"`
}
if err := c.BodyParser(&body); err != nil || body.URL == "" || body.Version == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "url과 version은 필수입니다"})
}
info, err := h.svc.Upsert(body.URL, body.Version, body.FileName, body.FileSize)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "업데이트에 실패했습니다"})
}
return c.JSON(info)
}

View File

@@ -0,0 +1,11 @@
package download
import "gorm.io/gorm"
type Info struct {
gorm.Model
URL string `gorm:"not null"`
Version string `gorm:"not null"`
FileName string `gorm:"not null"`
FileSize string `gorm:"not null"`
}

View File

@@ -0,0 +1,24 @@
package download
import "gorm.io/gorm"
type Repository struct {
db *gorm.DB
}
func NewRepository(db *gorm.DB) *Repository {
return &Repository{db: db}
}
func (r *Repository) GetLatest() (*Info, error) {
var info Info
err := r.db.Last(&info).Error
return &info, err
}
func (r *Repository) Save(info *Info) error {
if info.ID == 0 {
return r.db.Create(info).Error
}
return r.db.Save(info).Error
}

View File

@@ -0,0 +1,25 @@
package download
type Service struct {
repo *Repository
}
func NewService(repo *Repository) *Service {
return &Service{repo: repo}
}
func (s *Service) GetInfo() (*Info, error) {
return s.repo.GetLatest()
}
func (s *Service) Upsert(url, version, fileName, fileSize string) (*Info, error) {
info, err := s.repo.GetLatest()
if err != nil {
info = &Info{}
}
info.URL = url
info.Version = version
info.FileName = fileName
info.FileSize = fileSize
return info, s.repo.Save(info)
}