feat: implement achievements
This commit is contained in:
@@ -33,6 +33,8 @@ Temporary Items
|
|||||||
!.vscode/*.code-snippets
|
!.vscode/*.code-snippets
|
||||||
!*.code-workspace
|
!*.code-workspace
|
||||||
|
|
||||||
|
.idea/*
|
||||||
|
|
||||||
# Built Visual Studio Code Extensions
|
# Built Visual Studio Code Extensions
|
||||||
*.vsix
|
*.vsix
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"datarush/internal/achievements/config"
|
||||||
|
"datarush/internal/achievements/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cfg, err := config.Load()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to load config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
srv := server.New(cfg)
|
||||||
|
|
||||||
|
if err := srv.Start(); err != nil {
|
||||||
|
log.Fatalf("failed to start server: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
quit := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
<-quit
|
||||||
|
|
||||||
|
log.Println("shutting down achievements server...")
|
||||||
|
srv.Stop()
|
||||||
|
log.Println("achievements server stopped")
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
DROP TABLE IF EXISTS user_achievements;
|
||||||
|
DROP TABLE IF EXISTS achievements;
|
||||||
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS achievements (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT NOT NULL,
|
||||||
|
icon_url VARCHAR(512),
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS user_achievements (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
achievement_id UUID NOT NULL REFERENCES achievements(id) ON DELETE CASCADE,
|
||||||
|
received_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
UNIQUE(user_id, achievement_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_user_achievements_user_id ON user_achievements(user_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_user_achievements_achievement_id ON user_achievements(achievement_id);
|
||||||
|
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
GRPCPort int
|
||||||
|
GRPCEnableReflection bool
|
||||||
|
HTTPPort int
|
||||||
|
LogLevel string
|
||||||
|
DBHost string
|
||||||
|
DBPort int
|
||||||
|
DBUser string
|
||||||
|
DBPassword string
|
||||||
|
DBName string
|
||||||
|
AuthSvcAddr string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Load() (*Config, error) {
|
||||||
|
_ = godotenv.Load()
|
||||||
|
|
||||||
|
return &Config{
|
||||||
|
GRPCPort: mustGetInt("ACHIEVEMENTS_GRPC_PORT", 50057),
|
||||||
|
GRPCEnableReflection: mustGetBool("ACHIEVEMENTS_GRPC_ENABLE_REFLECTION", false),
|
||||||
|
HTTPPort: mustGetInt("ACHIEVEMENTS_HTTP_PORT", 8087),
|
||||||
|
LogLevel: getEnv("LOG_LEVEL", "info"),
|
||||||
|
DBHost: getEnv("POSTGRES_HOST", "localhost"),
|
||||||
|
DBPort: mustGetInt("POSTGRES_PORT", 5432),
|
||||||
|
DBUser: getEnv("POSTGRES_USERNAME", "postgres"),
|
||||||
|
DBPassword: getEnv("POSTGRES_PASSWORD", "postgres"),
|
||||||
|
DBName: getEnv("POSTGRES_DATABASE", "postgres"),
|
||||||
|
AuthSvcAddr: getEnv("AUTH_SVC_ADDR", "localhost:50051"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEnv(key, def string) string {
|
||||||
|
if val := os.Getenv(key); val != "" {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustGetInt(key string, def int) int {
|
||||||
|
val := getEnv(key, strconv.Itoa(def))
|
||||||
|
n, err := strconv.Atoi(val)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("invalid int for %s: %v", key, err)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustGetBool(key string, def bool) bool {
|
||||||
|
val := getEnv(key, strconv.FormatBool(def))
|
||||||
|
b, err := strconv.ParseBool(val)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("invalid bool for %s: %v", key, err)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Config) BuildPostgresConnStr() string {
|
||||||
|
return fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
|
||||||
|
c.DBHost, c.DBPort, c.DBUser, c.DBPassword, c.DBName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Config) BuildPostgresDSN() string {
|
||||||
|
return fmt.Sprintf("postgresql://%s:%s@%s/%s?sslmode=disable",
|
||||||
|
c.DBUser, c.DBPassword, net.JoinHostPort(c.DBHost, strconv.Itoa(c.DBPort)), c.DBName)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
pb "datarush/pkg/api/achievements"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AchievementService interface {
|
||||||
|
CreateAchievement(ctx context.Context, req *pb.Achievement) (*pb.Achievement, error)
|
||||||
|
GetAchievement(ctx context.Context, req *pb.GetAchievementRequest) (*pb.Achievement, error)
|
||||||
|
EditAchievement(ctx context.Context, req *pb.Achievement) (*pb.Achievement, error)
|
||||||
|
DeleteAchievement(ctx context.Context, req *pb.DeleteAchievementRequest) (*emptypb.Empty, error)
|
||||||
|
ListAchievements(ctx context.Context, req *emptypb.Empty) (*pb.ListAchievementsResponse, error)
|
||||||
|
GetUserAchievements(ctx context.Context, req *pb.GetUserAchievementsRequest) (*pb.GetUserAchievementsResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type AchievementHandler struct {
|
||||||
|
pb.UnimplementedAchievementsServiceServer
|
||||||
|
service AchievementService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAchievementHandler(service AchievementService) *AchievementHandler {
|
||||||
|
return &AchievementHandler{service: service}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AchievementHandler) CreateAchievement(ctx context.Context, req *pb.Achievement) (*pb.Achievement, error) {
|
||||||
|
return h.service.CreateAchievement(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AchievementHandler) GetAchievement(ctx context.Context, req *pb.GetAchievementRequest) (*pb.Achievement, error) {
|
||||||
|
return h.service.GetAchievement(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AchievementHandler) EditAchievement(ctx context.Context, req *pb.Achievement) (*pb.Achievement, error) {
|
||||||
|
return h.service.EditAchievement(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AchievementHandler) DeleteAchievement(ctx context.Context, req *pb.DeleteAchievementRequest) (*emptypb.Empty, error) {
|
||||||
|
return h.service.DeleteAchievement(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AchievementHandler) ListAchievements(ctx context.Context, req *emptypb.Empty) (*pb.ListAchievementsResponse, error) {
|
||||||
|
return h.service.ListAchievements(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AchievementHandler) GetUserAchievements(ctx context.Context, req *pb.GetUserAchievementsRequest) (*pb.GetUserAchievementsResponse, error) {
|
||||||
|
return h.service.GetUserAchievements(ctx, req)
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
package postgres
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
pb "datarush/pkg/api/achievements"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AchievementRepository struct {
|
||||||
|
db *sqlx.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAchievementRepository(db *sqlx.DB) *AchievementRepository {
|
||||||
|
return &AchievementRepository{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AchievementRepository) CreateAchievement(ctx context.Context, achievement *pb.Achievement) (*pb.Achievement, error) {
|
||||||
|
query := `INSERT INTO achievements (name, description, icon_url)
|
||||||
|
VALUES ($1, $2, $3) RETURNING id`
|
||||||
|
|
||||||
|
var createdAchievement pb.Achievement
|
||||||
|
err := r.db.QueryRowContext(ctx, query, achievement.Name, achievement.Description, achievement.IconUrl).
|
||||||
|
Scan(&createdAchievement.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
createdAchievement.Name = achievement.Name
|
||||||
|
createdAchievement.Description = achievement.Description
|
||||||
|
createdAchievement.IconUrl = achievement.IconUrl
|
||||||
|
|
||||||
|
return &createdAchievement, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AchievementRepository) GetAchievement(ctx context.Context, id string) (*pb.Achievement, error) {
|
||||||
|
query := `SELECT id, name, description, icon_url
|
||||||
|
FROM achievements WHERE id = $1`
|
||||||
|
|
||||||
|
var achievement pb.Achievement
|
||||||
|
err := r.db.QueryRowContext(ctx, query, id).
|
||||||
|
Scan(&achievement.Id, &achievement.Name, &achievement.Description, &achievement.IconUrl)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return nil, sql.ErrNoRows
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &achievement, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AchievementRepository) EditAchievement(ctx context.Context, achievement *pb.Achievement) (*pb.Achievement, error) {
|
||||||
|
query := `UPDATE achievements SET name = $1, description = $2, icon_url = $3, updated_at = NOW()
|
||||||
|
WHERE id = $4`
|
||||||
|
|
||||||
|
_, err := r.db.ExecContext(ctx, query, achievement.Name, achievement.Description, achievement.IconUrl, achievement.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return achievement, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AchievementRepository) DeleteAchievement(ctx context.Context, id string) error {
|
||||||
|
_, err := r.db.ExecContext(ctx, "DELETE FROM achievements WHERE id = $1", id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AchievementRepository) ListAchievements(ctx context.Context) ([]*pb.Achievement, error) {
|
||||||
|
query := `SELECT id, name, description, icon_url FROM achievements ORDER BY created_at DESC`
|
||||||
|
|
||||||
|
rows, err := r.db.QueryContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var achievements []*pb.Achievement
|
||||||
|
for rows.Next() {
|
||||||
|
var achievement pb.Achievement
|
||||||
|
|
||||||
|
err := rows.Scan(&achievement.Id, &achievement.Name, &achievement.Description, &achievement.IconUrl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
achievements = append(achievements, &achievement)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return achievements, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AchievementRepository) GetUserAchievements(ctx context.Context, userID string) ([]*pb.AchievementUser, error) {
|
||||||
|
query := `SELECT a.id, a.name, a.description, a.icon_url, ua.received_at
|
||||||
|
FROM user_achievements ua
|
||||||
|
INNER JOIN achievements a ON ua.achievement_id = a.id
|
||||||
|
WHERE ua.user_id = $1
|
||||||
|
ORDER BY ua.received_at DESC`
|
||||||
|
|
||||||
|
rows, err := r.db.QueryContext(ctx, query, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var userAchievements []*pb.AchievementUser
|
||||||
|
for rows.Next() {
|
||||||
|
var achievement pb.Achievement
|
||||||
|
var receivedAt time.Time
|
||||||
|
|
||||||
|
err := rows.Scan(
|
||||||
|
&achievement.Id,
|
||||||
|
&achievement.Name,
|
||||||
|
&achievement.Description,
|
||||||
|
&achievement.IconUrl,
|
||||||
|
&receivedAt,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
userAchievement := &pb.AchievementUser{
|
||||||
|
Achievement: &achievement,
|
||||||
|
ReceivedAt: timestamppb.New(receivedAt),
|
||||||
|
}
|
||||||
|
|
||||||
|
userAchievements = append(userAchievements, userAchievement)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return userAchievements, nil
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
pb "datarush/pkg/api/achievements"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AchievementRepository interface {
|
||||||
|
CreateAchievement(ctx context.Context, achievement *pb.Achievement) (*pb.Achievement, error)
|
||||||
|
GetAchievement(ctx context.Context, id string) (*pb.Achievement, error)
|
||||||
|
EditAchievement(ctx context.Context, achievement *pb.Achievement) (*pb.Achievement, error)
|
||||||
|
DeleteAchievement(ctx context.Context, id string) error
|
||||||
|
ListAchievements(ctx context.Context) ([]*pb.Achievement, error)
|
||||||
|
GetUserAchievements(ctx context.Context, userID string) ([]*pb.AchievementUser, error)
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"datarush/internal/achievements/config"
|
||||||
|
grpcHandlers "datarush/internal/achievements/handler/grpc"
|
||||||
|
achievementPostgresRepo "datarush/internal/achievements/repository/postgres"
|
||||||
|
"datarush/internal/achievements/service"
|
||||||
|
pb "datarush/pkg/api/achievements"
|
||||||
|
authpb "datarush/pkg/api/auth"
|
||||||
|
"datarush/pkg/interceptor"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
"google.golang.org/grpc/reflection"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
httpReadTimeout = 10 * time.Second
|
||||||
|
httpWriteTimeout = 10 * time.Second
|
||||||
|
httpIdleTimeout = 60 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
grpcServer *grpc.Server
|
||||||
|
config *config.Config
|
||||||
|
db *sqlx.DB
|
||||||
|
authConn *grpc.ClientConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cfg *config.Config) *Server {
|
||||||
|
return &Server{
|
||||||
|
config: cfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Start() error {
|
||||||
|
db, err := sqlx.Connect("postgres", s.config.BuildPostgresConnStr())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to connect to postgres: %w", err)
|
||||||
|
}
|
||||||
|
s.db = db
|
||||||
|
|
||||||
|
authConn, err := grpc.Dial(s.config.AuthSvcAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to connect to auth service: %w", err)
|
||||||
|
}
|
||||||
|
s.authConn = authConn
|
||||||
|
|
||||||
|
if err := s.registerGRPCServices(); err != nil {
|
||||||
|
return fmt.Errorf("failed to register gRPC services: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", s.config.GRPCPort))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to listen on grpc port: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("starting gRPC server on port %d", s.config.GRPCPort)
|
||||||
|
if err := s.grpcServer.Serve(lis); err != nil {
|
||||||
|
log.Fatalf("failed to serve gRPC: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) registerGRPCServices() error {
|
||||||
|
authClient := authpb.NewAuthServiceClient(s.authConn)
|
||||||
|
authInterceptor := interceptor.NewAuthInterceptor(authClient)
|
||||||
|
|
||||||
|
s.grpcServer = grpc.NewServer(
|
||||||
|
grpc.UnaryInterceptor(authInterceptor.Unary()),
|
||||||
|
)
|
||||||
|
achievementRepo := achievementPostgresRepo.NewAchievementRepository(s.db)
|
||||||
|
|
||||||
|
achievementService := service.NewAchievementService(achievementRepo)
|
||||||
|
|
||||||
|
achievementHandler := grpcHandlers.NewAchievementHandler(achievementService)
|
||||||
|
pb.RegisterAchievementsServiceServer(s.grpcServer, achievementHandler)
|
||||||
|
|
||||||
|
if s.config.GRPCEnableReflection {
|
||||||
|
reflection.Register(s.grpcServer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Stop() {
|
||||||
|
log.Println("shutting down achievements server...")
|
||||||
|
|
||||||
|
if s.grpcServer != nil {
|
||||||
|
s.grpcServer.GracefulStop()
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.db != nil {
|
||||||
|
if err := s.db.Close(); err != nil {
|
||||||
|
log.Printf("failed to close database: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.authConn != nil {
|
||||||
|
if err := s.authConn.Close(); err != nil {
|
||||||
|
log.Printf("failed to close auth connection: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("achievements server stopped")
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
pb "datarush/pkg/api/achievements"
|
||||||
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AchievementRepository interface {
|
||||||
|
CreateAchievement(ctx context.Context, achievement *pb.Achievement) (*pb.Achievement, error)
|
||||||
|
GetAchievement(ctx context.Context, id string) (*pb.Achievement, error)
|
||||||
|
EditAchievement(ctx context.Context, achievement *pb.Achievement) (*pb.Achievement, error)
|
||||||
|
DeleteAchievement(ctx context.Context, id string) error
|
||||||
|
ListAchievements(ctx context.Context) ([]*pb.Achievement, error)
|
||||||
|
GetUserAchievements(ctx context.Context, userID string) ([]*pb.AchievementUser, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type AchievementService struct {
|
||||||
|
repo AchievementRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAchievementService(repo AchievementRepository) *AchievementService {
|
||||||
|
return &AchievementService{repo: repo}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AchievementService) CreateAchievement(ctx context.Context, req *pb.Achievement) (*pb.Achievement, error) {
|
||||||
|
return s.repo.CreateAchievement(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AchievementService) GetAchievement(ctx context.Context, req *pb.GetAchievementRequest) (*pb.Achievement, error) {
|
||||||
|
achievement, err := s.repo.GetAchievement(ctx, req.Id)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return nil, errors.New("achievement not found")
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return achievement, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AchievementService) EditAchievement(ctx context.Context, req *pb.Achievement) (*pb.Achievement, error) {
|
||||||
|
_, err := s.repo.GetAchievement(ctx, req.Id)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return nil, errors.New("achievement not found")
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.repo.EditAchievement(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AchievementService) DeleteAchievement(ctx context.Context, req *pb.DeleteAchievementRequest) (*emptypb.Empty, error) {
|
||||||
|
err := s.repo.DeleteAchievement(ctx, req.AchievementId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &emptypb.Empty{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AchievementService) ListAchievements(ctx context.Context, req *emptypb.Empty) (*pb.ListAchievementsResponse, error) {
|
||||||
|
achievements, err := s.repo.ListAchievements(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &pb.ListAchievementsResponse{Achievements: achievements}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AchievementService) GetUserAchievements(ctx context.Context, req *pb.GetUserAchievementsRequest) (*pb.GetUserAchievementsResponse, error) {
|
||||||
|
userAchievements, err := s.repo.GetUserAchievements(ctx, req.UserId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &pb.GetUserAchievementsResponse{UserAchievements: userAchievements}, nil
|
||||||
|
}
|
||||||
@@ -47,3 +47,28 @@ func (c *AchievementsClient) GetUserAchievements(ctx context.Context, userID str
|
|||||||
}
|
}
|
||||||
return resp.UserAchievements, nil
|
return resp.UserAchievements, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *AchievementsClient) CreateAchievement(ctx context.Context, achievement *pb.Achievement) (*pb.Achievement, error) {
|
||||||
|
resp, err := c.client.CreateAchievement(ctx, achievement)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create achievement failed: %w", err)
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AchievementsClient) EditAchievement(ctx context.Context, achievement *pb.Achievement) (*pb.Achievement, error) {
|
||||||
|
resp, err := c.client.EditAchievement(ctx, achievement)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("edit achievement failed: %w", err)
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AchievementsClient) DeleteAchievement(ctx context.Context, achievementID string) error {
|
||||||
|
req := &pb.DeleteAchievementRequest{AchievementId: achievementID}
|
||||||
|
_, err := c.client.DeleteAchievement(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("delete achievement failed: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user