diff --git a/.forgejo/workflows/build-and-deploy.yml b/.forgejo/workflows/build-and-deploy.yml index dde9392..3c473d2 100644 --- a/.forgejo/workflows/build-and-deploy.yml +++ b/.forgejo/workflows/build-and-deploy.yml @@ -111,11 +111,6 @@ jobs: POSTGRES_VERSION: 18 LOG_LEVEL: ${{ vars.LOG_LEVEL }} DATABASE_URL: ${{ secrets.DATABASE_URL }} - SERVICE_ADDRESS: ${{ vars.SERVICE_ADDRESS }} - RABBITMQ_URL: ${{ secrets.RABBITMQ_URL }} - JWT_PRIVATE_KEY: ${{ secrets.JWT_PRIVATE_KEY }} - RABBITMQ_DEFAULT_USER: ${{ secrets.RABBITMQ_USER }} - RABBITMQ_DEFAULT_PASS: ${{ secrets.RABBITMQ_PASSWORD }} steps: - name: Checkout code diff --git a/api/_build/openapi.yaml b/api/_build/openapi.yaml index e096beb..e85ddf9 100644 --- a/api/_build/openapi.yaml +++ b/api/_build/openapi.yaml @@ -120,8 +120,6 @@ paths: description: Title not found '500': description: Unknown server error - security: - - JwtAuthCookies: [] '/users/{user_id}': get: operationId: getUsersId @@ -225,8 +223,6 @@ paths: description: 'Unprocessable Entity — semantic errors not caught by schema (e.g., invalid `avatar_id`)' '500': description: Unknown server error - security: - - XsrfAuthHeader: [] '/users/{user_id}/titles': get: operationId: getUserTitles @@ -448,8 +444,6 @@ paths: description: User or Title not found '500': description: Internal server error - security: - - XsrfAuthHeader: [] delete: operationId: deleteUserTitle summary: Delete a usertitle @@ -478,8 +472,6 @@ paths: description: User or Title not found '500': description: Internal server error - security: - - XsrfAuthHeader: [] components: parameters: cursor: @@ -740,11 +732,3 @@ components: Review: type: object additionalProperties: true - securitySchemes: - XsrfAuthHeader: - type: apiKey - in: header - name: X-XSRF-TOKEN - description: | - Anti-CSRF token. Must match the `XSRF-TOKEN` cookie. - Required for all state-changing requests (POST/PUT/PATCH/DELETE). diff --git a/api/api.gen.go b/api/api.gen.go index 459a3e4..c8fd9aa 100644 --- a/api/api.gen.go +++ b/api/api.gen.go @@ -16,11 +16,6 @@ import ( openapi_types "github.com/oapi-codegen/runtime/types" ) -const ( - JwtAuthCookiesScopes = "JwtAuthCookies.Scopes" - XsrfAuthHeaderScopes = "XsrfAuthHeader.Scopes" -) - // Defines values for ReleaseSeason. const ( Fall ReleaseSeason = "fall" @@ -436,8 +431,6 @@ func (siw *ServerInterfaceWrapper) GetTitle(c *gin.Context) { return } - c.Set(JwtAuthCookiesScopes, []string{}) - // Parameter object where we will unmarshal all parameters from the context var params GetTitleParams @@ -508,8 +501,6 @@ func (siw *ServerInterfaceWrapper) UpdateUser(c *gin.Context) { return } - c.Set(XsrfAuthHeaderScopes, []string{}) - for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -690,8 +681,6 @@ func (siw *ServerInterfaceWrapper) DeleteUserTitle(c *gin.Context) { return } - c.Set(XsrfAuthHeaderScopes, []string{}) - for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { @@ -758,8 +747,6 @@ func (siw *ServerInterfaceWrapper) UpdateUserTitle(c *gin.Context) { return } - c.Set(XsrfAuthHeaderScopes, []string{}) - for _, middleware := range siw.HandlerMiddlewares { middleware(c) if c.IsAborted() { diff --git a/api/openapi.yaml b/api/openapi.yaml index d84797f..08a4d54 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -23,5 +23,3 @@ components: $ref: "./parameters/_index.yaml" schemas: $ref: "./schemas/_index.yaml" - securitySchemes: - $ref: "./securitySchemes/_index.yaml" \ No newline at end of file diff --git a/api/paths/titles-id.yaml b/api/paths/titles-id.yaml index f1b9c55..235743f 100644 --- a/api/paths/titles-id.yaml +++ b/api/paths/titles-id.yaml @@ -1,7 +1,5 @@ get: summary: Get title description - security: - - JwtAuthCookies: [] operationId: getTitle parameters: - in: path diff --git a/api/paths/users-id-titles-id.yaml b/api/paths/users-id-titles-id.yaml index 1da2b81..b4ad884 100644 --- a/api/paths/users-id-titles-id.yaml +++ b/api/paths/users-id-titles-id.yaml @@ -34,8 +34,6 @@ patch: summary: Update a usertitle description: User updating title list of watched operationId: updateUserTitle - security: - - XsrfAuthHeader: [] parameters: - in: path name: user_id @@ -83,8 +81,6 @@ delete: summary: Delete a usertitle description: User deleting title from list of watched operationId: deleteUserTitle - security: - - XsrfAuthHeader: [] parameters: - in: path name: user_id diff --git a/api/paths/users-id.yaml b/api/paths/users-id.yaml index 701df6b..fe62e46 100644 --- a/api/paths/users-id.yaml +++ b/api/paths/users-id.yaml @@ -33,10 +33,7 @@ patch: Password updates must be done via the dedicated auth-service (`/auth/`). Fields not provided in the request body remain unchanged. operationId: updateUser - security: - - XsrfAuthHeader: [] parameters: - # - $ref: '../parameters/xsrf_token_header.yaml' - name: user_id in: path required: true diff --git a/api/schemas/JWTAuth.yaml b/api/schemas/JWTAuth.yaml deleted file mode 100644 index 63c3baa..0000000 --- a/api/schemas/JWTAuth.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# type: apiKey -# in: cookie -# name: access_token -# scheme: bearer -# bearerFormat: JWT -# description: | -# JWT access token sent in `Cookie: access_token=...`. \ No newline at end of file diff --git a/api/schemas/_index.yaml b/api/schemas/_index.yaml index 0cc0f9d..d893ced 100644 --- a/api/schemas/_index.yaml +++ b/api/schemas/_index.yaml @@ -24,5 +24,3 @@ User: $ref: "./User.yaml" UserTitle: $ref: "./UserTitle.yaml" -# JwtAuth: -# $ref: "./JWTAuth.yaml" diff --git a/api/securitySchemes/_index.yaml b/api/securitySchemes/_index.yaml deleted file mode 100644 index ecc0ff6..0000000 --- a/api/securitySchemes/_index.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# accessToken: -# $ref: "./access_token.yaml" -# csrfToken: -# $ref: "./xsrf_token_cookie.yaml" -XsrfAuthHeader: - type: apiKey - in: header - name: X-XSRF-TOKEN - description: | - Anti-CSRF token. Must match the `XSRF-TOKEN` cookie. - Required for all state-changing requests (POST/PUT/PATCH/DELETE). \ No newline at end of file diff --git a/auth/auth.gen.go b/auth/auth.gen.go index b7cd839..7276545 100644 --- a/auth/auth.gen.go +++ b/auth/auth.gen.go @@ -13,32 +13,32 @@ import ( strictgin "github.com/oapi-codegen/runtime/strictmiddleware/gin" ) -// PostSignInJSONBody defines parameters for PostSignIn. -type PostSignInJSONBody struct { +// PostAuthSignInJSONBody defines parameters for PostAuthSignIn. +type PostAuthSignInJSONBody struct { Nickname string `json:"nickname"` Pass string `json:"pass"` } -// PostSignUpJSONBody defines parameters for PostSignUp. -type PostSignUpJSONBody struct { +// PostAuthSignUpJSONBody defines parameters for PostAuthSignUp. +type PostAuthSignUpJSONBody struct { Nickname string `json:"nickname"` Pass string `json:"pass"` } -// PostSignInJSONRequestBody defines body for PostSignIn for application/json ContentType. -type PostSignInJSONRequestBody PostSignInJSONBody +// PostAuthSignInJSONRequestBody defines body for PostAuthSignIn for application/json ContentType. +type PostAuthSignInJSONRequestBody PostAuthSignInJSONBody -// PostSignUpJSONRequestBody defines body for PostSignUp for application/json ContentType. -type PostSignUpJSONRequestBody PostSignUpJSONBody +// PostAuthSignUpJSONRequestBody defines body for PostAuthSignUp for application/json ContentType. +type PostAuthSignUpJSONRequestBody PostAuthSignUpJSONBody // ServerInterface represents all server handlers. type ServerInterface interface { // Sign in a user and return JWT - // (POST /sign-in) - PostSignIn(c *gin.Context) + // (POST /auth/sign-in) + PostAuthSignIn(c *gin.Context) // Sign up a new user - // (POST /sign-up) - PostSignUp(c *gin.Context) + // (POST /auth/sign-up) + PostAuthSignUp(c *gin.Context) } // ServerInterfaceWrapper converts contexts to parameters. @@ -50,8 +50,8 @@ type ServerInterfaceWrapper struct { type MiddlewareFunc func(c *gin.Context) -// PostSignIn operation middleware -func (siw *ServerInterfaceWrapper) PostSignIn(c *gin.Context) { +// PostAuthSignIn operation middleware +func (siw *ServerInterfaceWrapper) PostAuthSignIn(c *gin.Context) { for _, middleware := range siw.HandlerMiddlewares { middleware(c) @@ -60,11 +60,11 @@ func (siw *ServerInterfaceWrapper) PostSignIn(c *gin.Context) { } } - siw.Handler.PostSignIn(c) + siw.Handler.PostAuthSignIn(c) } -// PostSignUp operation middleware -func (siw *ServerInterfaceWrapper) PostSignUp(c *gin.Context) { +// PostAuthSignUp operation middleware +func (siw *ServerInterfaceWrapper) PostAuthSignUp(c *gin.Context) { for _, middleware := range siw.HandlerMiddlewares { middleware(c) @@ -73,7 +73,7 @@ func (siw *ServerInterfaceWrapper) PostSignUp(c *gin.Context) { } } - siw.Handler.PostSignUp(c) + siw.Handler.PostAuthSignUp(c) } // GinServerOptions provides options for the Gin server. @@ -103,54 +103,54 @@ func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options ErrorHandler: errorHandler, } - router.POST(options.BaseURL+"/sign-in", wrapper.PostSignIn) - router.POST(options.BaseURL+"/sign-up", wrapper.PostSignUp) + router.POST(options.BaseURL+"/auth/sign-in", wrapper.PostAuthSignIn) + router.POST(options.BaseURL+"/auth/sign-up", wrapper.PostAuthSignUp) } -type PostSignInRequestObject struct { - Body *PostSignInJSONRequestBody +type PostAuthSignInRequestObject struct { + Body *PostAuthSignInJSONRequestBody } -type PostSignInResponseObject interface { - VisitPostSignInResponse(w http.ResponseWriter) error +type PostAuthSignInResponseObject interface { + VisitPostAuthSignInResponse(w http.ResponseWriter) error } -type PostSignIn200JSONResponse struct { +type PostAuthSignIn200JSONResponse struct { UserId int64 `json:"user_id"` UserName string `json:"user_name"` } -func (response PostSignIn200JSONResponse) VisitPostSignInResponse(w http.ResponseWriter) error { +func (response PostAuthSignIn200JSONResponse) VisitPostAuthSignInResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) return json.NewEncoder(w).Encode(response) } -type PostSignIn401JSONResponse struct { +type PostAuthSignIn401JSONResponse struct { Error *string `json:"error,omitempty"` } -func (response PostSignIn401JSONResponse) VisitPostSignInResponse(w http.ResponseWriter) error { +func (response PostAuthSignIn401JSONResponse) VisitPostAuthSignInResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(401) return json.NewEncoder(w).Encode(response) } -type PostSignUpRequestObject struct { - Body *PostSignUpJSONRequestBody +type PostAuthSignUpRequestObject struct { + Body *PostAuthSignUpJSONRequestBody } -type PostSignUpResponseObject interface { - VisitPostSignUpResponse(w http.ResponseWriter) error +type PostAuthSignUpResponseObject interface { + VisitPostAuthSignUpResponse(w http.ResponseWriter) error } -type PostSignUp200JSONResponse struct { +type PostAuthSignUp200JSONResponse struct { UserId int64 `json:"user_id"` } -func (response PostSignUp200JSONResponse) VisitPostSignUpResponse(w http.ResponseWriter) error { +func (response PostAuthSignUp200JSONResponse) VisitPostAuthSignUpResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) @@ -160,11 +160,11 @@ func (response PostSignUp200JSONResponse) VisitPostSignUpResponse(w http.Respons // StrictServerInterface represents all server handlers. type StrictServerInterface interface { // Sign in a user and return JWT - // (POST /sign-in) - PostSignIn(ctx context.Context, request PostSignInRequestObject) (PostSignInResponseObject, error) + // (POST /auth/sign-in) + PostAuthSignIn(ctx context.Context, request PostAuthSignInRequestObject) (PostAuthSignInResponseObject, error) // Sign up a new user - // (POST /sign-up) - PostSignUp(ctx context.Context, request PostSignUpRequestObject) (PostSignUpResponseObject, error) + // (POST /auth/sign-up) + PostAuthSignUp(ctx context.Context, request PostAuthSignUpRequestObject) (PostAuthSignUpResponseObject, error) } type StrictHandlerFunc = strictgin.StrictGinHandlerFunc @@ -179,11 +179,11 @@ type strictHandler struct { middlewares []StrictMiddlewareFunc } -// PostSignIn operation middleware -func (sh *strictHandler) PostSignIn(ctx *gin.Context) { - var request PostSignInRequestObject +// PostAuthSignIn operation middleware +func (sh *strictHandler) PostAuthSignIn(ctx *gin.Context) { + var request PostAuthSignInRequestObject - var body PostSignInJSONRequestBody + var body PostAuthSignInJSONRequestBody if err := ctx.ShouldBindJSON(&body); err != nil { ctx.Status(http.StatusBadRequest) ctx.Error(err) @@ -192,10 +192,10 @@ func (sh *strictHandler) PostSignIn(ctx *gin.Context) { request.Body = &body handler := func(ctx *gin.Context, request interface{}) (interface{}, error) { - return sh.ssi.PostSignIn(ctx, request.(PostSignInRequestObject)) + return sh.ssi.PostAuthSignIn(ctx, request.(PostAuthSignInRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "PostSignIn") + handler = middleware(handler, "PostAuthSignIn") } response, err := handler(ctx, request) @@ -203,8 +203,8 @@ func (sh *strictHandler) PostSignIn(ctx *gin.Context) { if err != nil { ctx.Error(err) ctx.Status(http.StatusInternalServerError) - } else if validResponse, ok := response.(PostSignInResponseObject); ok { - if err := validResponse.VisitPostSignInResponse(ctx.Writer); err != nil { + } else if validResponse, ok := response.(PostAuthSignInResponseObject); ok { + if err := validResponse.VisitPostAuthSignInResponse(ctx.Writer); err != nil { ctx.Error(err) } } else if response != nil { @@ -212,11 +212,11 @@ func (sh *strictHandler) PostSignIn(ctx *gin.Context) { } } -// PostSignUp operation middleware -func (sh *strictHandler) PostSignUp(ctx *gin.Context) { - var request PostSignUpRequestObject +// PostAuthSignUp operation middleware +func (sh *strictHandler) PostAuthSignUp(ctx *gin.Context) { + var request PostAuthSignUpRequestObject - var body PostSignUpJSONRequestBody + var body PostAuthSignUpJSONRequestBody if err := ctx.ShouldBindJSON(&body); err != nil { ctx.Status(http.StatusBadRequest) ctx.Error(err) @@ -225,10 +225,10 @@ func (sh *strictHandler) PostSignUp(ctx *gin.Context) { request.Body = &body handler := func(ctx *gin.Context, request interface{}) (interface{}, error) { - return sh.ssi.PostSignUp(ctx, request.(PostSignUpRequestObject)) + return sh.ssi.PostAuthSignUp(ctx, request.(PostAuthSignUpRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "PostSignUp") + handler = middleware(handler, "PostAuthSignUp") } response, err := handler(ctx, request) @@ -236,8 +236,8 @@ func (sh *strictHandler) PostSignUp(ctx *gin.Context) { if err != nil { ctx.Error(err) ctx.Status(http.StatusInternalServerError) - } else if validResponse, ok := response.(PostSignUpResponseObject); ok { - if err := validResponse.VisitPostSignUpResponse(ctx.Writer); err != nil { + } else if validResponse, ok := response.(PostAuthSignUpResponseObject); ok { + if err := validResponse.VisitPostAuthSignUpResponse(ctx.Writer); err != nil { ctx.Error(err) } } else if response != nil { diff --git a/auth/openapi-auth.yaml b/auth/openapi-auth.yaml index 5f3ebd6..239b03b 100644 --- a/auth/openapi-auth.yaml +++ b/auth/openapi-auth.yaml @@ -7,7 +7,7 @@ servers: - url: /auth paths: - /sign-up: + /auth/sign-up: post: summary: Sign up a new user tags: [Auth] @@ -38,7 +38,7 @@ paths: type: integer format: int64 - /sign-in: + /auth/sign-in: post: summary: Sign in a user and return JWT tags: [Auth] diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 82116eb..79ad2f5 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -47,9 +47,6 @@ services: environment: LOG_LEVEL: ${LOG_LEVEL} DATABASE_URL: ${DATABASE_URL} - SERVICE_ADDRESS: ${SERVICE_ADDRESS} - RABBITMQ_URL: ${RABBITMQ_URL} - JWT_PRIVATE_KEY: ${JWT_PRIVATE_KEY} ports: - "8080:8080" depends_on: @@ -65,8 +62,6 @@ services: environment: LOG_LEVEL: ${LOG_LEVEL} DATABASE_URL: ${DATABASE_URL} - SERVICE_ADDRESS: ${SERVICE_ADDRESS} - JWT_PRIVATE_KEY: ${JWT_PRIVATE_KEY} ports: - "8082:8082" depends_on: diff --git a/modules/auth/handlers/handlers.go b/modules/auth/handlers/handlers.go index 03df151..261826c 100644 --- a/modules/auth/handlers/handlers.go +++ b/modules/auth/handlers/handlers.go @@ -2,8 +2,6 @@ package handlers import ( "context" - "crypto/rand" - "encoding/base64" "fmt" "net/http" auth "nyanimedb/auth" @@ -17,13 +15,15 @@ import ( log "github.com/sirupsen/logrus" ) +var accessSecret = []byte("my_access_secret_key") +var refreshSecret = []byte("my_refresh_secret_key") + type Server struct { - db *sqlc.Queries - JwtPrivateKey string + db *sqlc.Queries } -func NewServer(db *sqlc.Queries, JwtPrivatekey string) Server { - return Server{db: db, JwtPrivateKey: JwtPrivatekey} +func NewServer(db *sqlc.Queries) Server { + return Server{db: db} } func parseInt64(s string) (int32, error) { @@ -47,15 +47,15 @@ func CheckPassword(password, hash string) (bool, error) { return argon2id.ComparePasswordAndHash(password, hash) } -func (s Server) generateTokens(userID string) (accessToken string, refreshToken string, csrfToken string, err error) { +func generateTokens(userID string) (accessToken string, refreshToken string, err error) { accessClaims := jwt.MapClaims{ "user_id": userID, "exp": time.Now().Add(15 * time.Minute).Unix(), } at := jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims) - accessToken, err = at.SignedString([]byte(s.JwtPrivateKey)) + accessToken, err = at.SignedString(accessSecret) if err != nil { - return "", "", "", err + return "", "", err } refreshClaims := jwt.MapClaims{ @@ -63,22 +63,15 @@ func (s Server) generateTokens(userID string) (accessToken string, refreshToken "exp": time.Now().Add(7 * 24 * time.Hour).Unix(), } rt := jwt.NewWithClaims(jwt.SigningMethodHS256, refreshClaims) - refreshToken, err = rt.SignedString([]byte(s.JwtPrivateKey)) + refreshToken, err = rt.SignedString(refreshSecret) if err != nil { - return "", "", "", err + return "", "", err } - csrfBytes := make([]byte, 32) - _, err = rand.Read(csrfBytes) - if err != nil { - return "", "", "", err - } - csrfToken = base64.RawURLEncoding.EncodeToString(csrfBytes) - - return accessToken, refreshToken, csrfToken, nil + return accessToken, refreshToken, nil } -func (s Server) PostSignUp(ctx context.Context, req auth.PostSignUpRequestObject) (auth.PostSignUpResponseObject, error) { +func (s Server) PostAuthSignUp(ctx context.Context, req auth.PostAuthSignUpRequestObject) (auth.PostAuthSignUpResponseObject, error) { passhash, err := HashPassword(req.Body.Pass) if err != nil { log.Errorf("failed to hash password: %v", err) @@ -94,17 +87,17 @@ func (s Server) PostSignUp(ctx context.Context, req auth.PostSignUpRequestObject // TODO: check err and retyrn 400/500 } - return auth.PostSignUp200JSONResponse{ + return auth.PostAuthSignUp200JSONResponse{ UserId: user_id, }, nil } -func (s Server) PostSignIn(ctx context.Context, req auth.PostSignInRequestObject) (auth.PostSignInResponseObject, error) { +func (s Server) PostAuthSignIn(ctx context.Context, req auth.PostAuthSignInRequestObject) (auth.PostAuthSignInResponseObject, error) { ginCtx, ok := ctx.Value(gin.ContextKey).(*gin.Context) if !ok { log.Print("failed to get gin context") // TODO: change to 500 - return auth.PostSignIn200JSONResponse{}, fmt.Errorf("failed to get gin.Context from context.Context") + return auth.PostAuthSignIn200JSONResponse{}, fmt.Errorf("failed to get gin.Context from context.Context") } user, err := s.db.GetUserByNickname(context.Background(), req.Body.Nickname) @@ -120,12 +113,12 @@ func (s Server) PostSignIn(ctx context.Context, req auth.PostSignInRequestObject } if !ok { err_msg := "invalid credentials" - return auth.PostSignIn401JSONResponse{ + return auth.PostAuthSignIn401JSONResponse{ Error: &err_msg, }, nil } - accessToken, refreshToken, csrfToken, err := s.generateTokens(req.Body.Nickname) + accessToken, refreshToken, err := generateTokens(req.Body.Nickname) if err != nil { log.Errorf("failed to generate tokens for user %s: %v", req.Body.Nickname, err) // TODO: return 500 @@ -133,11 +126,10 @@ func (s Server) PostSignIn(ctx context.Context, req auth.PostSignInRequestObject // TODO: check cookie settings carefully ginCtx.SetSameSite(http.SameSiteStrictMode) - ginCtx.SetCookie("access_token", accessToken, 900, "/api", "", false, true) - ginCtx.SetCookie("refresh_token", refreshToken, 1209600, "/auth", "", false, true) - ginCtx.SetCookie("xsrf_token", csrfToken, 1209600, "/api", "", false, false) + ginCtx.SetCookie("access_token", accessToken, 604800, "/auth", "", false, true) + ginCtx.SetCookie("refresh_token", refreshToken, 604800, "/api", "", false, true) - result := auth.PostSignIn200JSONResponse{ + result := auth.PostAuthSignIn200JSONResponse{ UserId: user.ID, UserName: user.Nickname, } diff --git a/modules/auth/helpers.go b/modules/auth/helpers.go deleted file mode 100644 index 9c3ab36..0000000 --- a/modules/auth/helpers.go +++ /dev/null @@ -1,33 +0,0 @@ -package main - -import ( - "fmt" - "reflect" -) - -func setField(obj interface{}, name string, value interface{}) error { - v := reflect.ValueOf(obj) - - if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct { - return fmt.Errorf("expected pointer to a struct") - } - - v = v.Elem() - field := v.FieldByName(name) - - if !field.IsValid() { - return fmt.Errorf("no such field: %s", name) - } - if !field.CanSet() { - return fmt.Errorf("cannot set field: %s", name) - } - - val := reflect.ValueOf(value) - - if field.Type() != val.Type() { - return fmt.Errorf("provided value type (%s) doesn't match field type (%s)", val.Type(), field.Type()) - } - - field.Set(val) - return nil -} diff --git a/modules/auth/main.go b/modules/auth/main.go index 7305b7d..7554f42 100644 --- a/modules/auth/main.go +++ b/modules/auth/main.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "os" - "reflect" "time" auth "nyanimedb/auth" @@ -14,24 +13,12 @@ import ( "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" "github.com/jackc/pgx/v5/pgxpool" - "github.com/pelletier/go-toml/v2" - log "github.com/sirupsen/logrus" ) var AppConfig Config func main() { - if len(os.Args) != 2 { - AppConfig.Mode = "env" - } else { - AppConfig.Mode = "argv" - } - - err := InitConfig() - if err != nil { - log.Fatalf("Failed to init config: %v\n", err) - } - + // TODO: env args r := gin.Default() pool, err := pgxpool.New(context.Background(), os.Getenv("DATABASE_URL")) @@ -42,11 +29,10 @@ func main() { var queries *sqlc.Queries = sqlc.New(pool) - server := handlers.NewServer(queries, AppConfig.JwtPrivateKey) + server := handlers.NewServer(queries) - log.Info("allow origins:", AppConfig.ServiceAddress) r.Use(cors.New(cors.Config{ - AllowOrigins: []string{"*"}, + AllowOrigins: []string{"*"}, // allow all origins, change to specific domains in production AllowMethods: []string{"GET", "POST", "PUT", "DELETE"}, AllowHeaders: []string{"Origin", "Content-Type", "Accept"}, ExposeHeaders: []string{"Content-Length"}, @@ -61,41 +47,3 @@ func main() { r.Run(":8082") } - -func InitConfig() error { - if AppConfig.Mode == "argv" { - content, err := os.ReadFile(os.Args[1]) - if err != nil { - return err - } - - toml.Unmarshal(content, &AppConfig) - - fmt.Printf("%+v\n", AppConfig) - - return nil - } else if AppConfig.Mode == "env" { - f := reflect.ValueOf(AppConfig) - - for i := 0; i < f.NumField(); i++ { - field := f.Type().Field(i) - tag := field.Tag - env_var := tag.Get("env") - fmt.Printf("Field: %v.\nEnvironment variable: %v.\n", field.Name, env_var) - if env_var != "" { - env_value, exists := os.LookupEnv(env_var) - if !exists { - return fmt.Errorf("there is no env variable %s", env_var) - } - err := setField(&AppConfig, field.Name, env_value) - if err != nil { - return fmt.Errorf("failed to set config field %s: %v", field.Name, err) - } - } - } - - return nil - } else { - return fmt.Errorf("incorrect config mode") - } -} diff --git a/modules/auth/types.go b/modules/auth/types.go index 694843e..038b179 100644 --- a/modules/auth/types.go +++ b/modules/auth/types.go @@ -1,9 +1,6 @@ package main type Config struct { - Mode string - ServiceAddress string `toml:"ServiceAddress" env:"SERVICE_ADDRESS"` - DdUrl string `toml:"DbUrl" env:"DATABASE_URL"` - JwtPrivateKey string `toml:"JwtPrivateKey" env:"JWT_PRIVATE_KEY"` - LogLevel string `toml:"LogLevel" env:"LOG_LEVEL"` + JwtPrivateKey string + LogLevel string `toml:"LogLevel" env:"LOG_LEVEL"` } diff --git a/modules/backend/main.go b/modules/backend/main.go index b833cf9..9f992a5 100644 --- a/modules/backend/main.go +++ b/modules/backend/main.go @@ -11,7 +11,6 @@ import ( oapi "nyanimedb/api" handlers "nyanimedb/modules/backend/handlers" - middleware "nyanimedb/modules/backend/middlewares" "nyanimedb/modules/backend/rmq" "github.com/gin-contrib/cors" @@ -25,18 +24,18 @@ import ( var AppConfig Config func main() { - if len(os.Args) != 2 { - AppConfig.Mode = "env" - } else { - AppConfig.Mode = "argv" - } + // if len(os.Args) != 2 { + // AppConfig.Mode = "env" + // } else { + // AppConfig.Mode = "argv" + // } - err := InitConfig() - if err != nil { - log.Fatalf("Failed to init config: %v\n", err) - } + // err := InitConfig() + // if err != nil { + // log.Fatalf("Failed to init config: %v\n", err) + // } - pool, err := pgxpool.New(context.Background(), AppConfig.DdUrl) + pool, err := pgxpool.New(context.Background(), os.Getenv("DATABASE_URL")) if err != nil { fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err) os.Exit(1) @@ -46,12 +45,15 @@ func main() { r := gin.Default() - r.Use(middleware.CSRFMiddleware()) - r.Use(middleware.JWTAuthMiddleware(AppConfig.JwtPrivateKey)) - queries := sqlc.New(pool) - rmqConn, err := amqp091.Dial(AppConfig.RmqURL) + // === RabbitMQ setup === + rmqURL := os.Getenv("RABBITMQ_URL") + if rmqURL == "" { + rmqURL = "amqp://guest:guest@rabbitmq:5672/" + } + + rmqConn, err := amqp091.Dial(rmqURL) if err != nil { log.Fatalf("Failed to connect to RabbitMQ: %v", err) } @@ -61,12 +63,12 @@ func main() { rpcClient := rmq.NewRPCClient(rmqConn, 30*time.Second) server := handlers.NewServer(queries, publisher, rpcClient) + // r.LoadHTMLGlob("templates/*") r.Use(cors.New(cors.Config{ - AllowOrigins: []string{AppConfig.ServiceAddress}, - // AllowOrigins: []string{"*"}, + AllowOrigins: []string{"*"}, // allow all origins, change to specific domains in production AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "PATCH"}, - AllowHeaders: []string{"Origin", "Content-Type", "Accept", "X-XSRF-TOKEN"}, + AllowHeaders: []string{"Origin", "Content-Type", "Accept"}, ExposeHeaders: []string{"Content-Length"}, AllowCredentials: true, MaxAge: 12 * time.Hour, @@ -74,7 +76,7 @@ func main() { oapi.RegisterHandlers(r, oapi.NewStrictHandler( server, - + // сюда можно добавить middlewares, если нужно []oapi.StrictMiddlewareFunc{}, )) diff --git a/modules/backend/middlewares/access.go b/modules/backend/middlewares/access.go deleted file mode 100644 index 73200e8..0000000 --- a/modules/backend/middlewares/access.go +++ /dev/null @@ -1,109 +0,0 @@ -package middleware - -import ( - "context" - "errors" - "net/http" - - "github.com/gin-gonic/gin" - "github.com/golang-jwt/jwt/v5" -) - -// ctxKey — приватный тип для ключа контекста -type ctxKey struct{} - -// ginContextKey — уникальный ключ для хранения *gin.Context -var ginContextKey = &ctxKey{} - -// GinContextToContext сохраняет *gin.Context в context.Context запроса -func GinContextToContext(c *gin.Context) { - ctx := context.WithValue(c.Request.Context(), ginContextKey, c) - c.Request = c.Request.WithContext(ctx) -} - -// GinContextFromContext извлекает *gin.Context из context.Context -func GinContextFromContext(ctx context.Context) (*gin.Context, bool) { - ginCtx, ok := ctx.Value(ginContextKey).(*gin.Context) - return ginCtx, ok -} - -func JWTAuthMiddleware(secret string) gin.HandlerFunc { - return func(c *gin.Context) { - // 1. Получаем access_token из cookie - tokenStr, err := c.Cookie("access_token") - if err != nil { - abortWithJSON(c, http.StatusUnauthorized, "missing access_token cookie") - return - } - - // 2. Парсим токен с MapClaims - token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) { - if t.Method != jwt.SigningMethodHS256 { - return nil, errors.New("unexpected signing method: " + t.Method.Alg()) - } - return []byte(secret), nil // ← конвертируем string → []byte - }) - if err != nil { - abortWithJSON(c, http.StatusUnauthorized, "invalid token: "+err.Error()) - return - } - - // 3. Проверяем валидность - if !token.Valid { - abortWithJSON(c, http.StatusUnauthorized, "token is invalid") - return - } - - // 4. Извлекаем user_id из claims - claims, ok := token.Claims.(jwt.MapClaims) - if !ok { - abortWithJSON(c, http.StatusUnauthorized, "invalid claims format") - return - } - - userID, ok := claims["user_id"].(string) - if !ok || userID == "" { - abortWithJSON(c, http.StatusUnauthorized, "user_id claim missing or invalid") - return - } - - // 5. Сохраняем в контексте - c.Set("user_id", userID) - - // 6. Для oapi-codegen — кладём gin.Context в request context - GinContextToContext(c) - - c.Next() - } -} - -// Вспомогательные функции (без изменений) -func UserIDFromGin(c *gin.Context) (string, bool) { - id, exists := c.Get("user_id") - if !exists { - return "", false - } - if s, ok := id.(string); ok { - return s, true - } - return "", false -} - -func UserIDFromContext(ctx context.Context) (string, error) { - ginCtx, ok := GinContextFromContext(ctx) - if !ok { - return "", errors.New("gin context not found") - } - userID, ok := UserIDFromGin(ginCtx) - if !ok { - return "", errors.New("user_id not found in context") - } - return userID, nil -} - -func abortWithJSON(c *gin.Context, code int, message string) { - c.AbortWithStatusJSON(code, gin.H{ - "error": "unauthorized", - "message": message, - }) -} diff --git a/modules/backend/middlewares/csrf.go b/modules/backend/middlewares/csrf.go deleted file mode 100644 index 41fad7b..0000000 --- a/modules/backend/middlewares/csrf.go +++ /dev/null @@ -1,70 +0,0 @@ -package middleware - -import ( - "crypto/subtle" - "net/http" - - "github.com/gin-gonic/gin" -) - -// CSRFMiddleware для Gin -func CSRFMiddleware() gin.HandlerFunc { - return func(c *gin.Context) { - // Пропускаем безопасные методы - if !isStateChangingMethod(c.Request.Method) { - c.Next() - return - } - - // 1. Получаем токен из заголовка - headerToken := c.GetHeader("X-XSRF-TOKEN") - if headerToken == "" { - c.AbortWithStatusJSON(http.StatusForbidden, gin.H{ - "error": "missing X-XSRF-TOKEN header", - }) - return - } - - // 2. Получаем токен из cookie - cookie, err := c.Cookie("xsrf_token") - if err != nil { - c.AbortWithStatusJSON(http.StatusForbidden, gin.H{ - "error": "missing xsrf_token cookie", - }) - return - } - - // 3. Безопасное сравнение - if subtle.ConstantTimeCompare([]byte(headerToken), []byte(cookie)) != 1 { - c.AbortWithStatusJSON(http.StatusForbidden, gin.H{ - "error": "CSRF token mismatch", - }) - return - } - - // 4. Опционально: сохраняем токен в контексте - c.Set("csrf_token", headerToken) - c.Next() - } -} - -func isStateChangingMethod(method string) bool { - switch method { - case http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete: - return true - default: - return false - } -} - -// CSRFTokenFromGin извлекает токен из Gin context -func CSRFTokenFromGin(c *gin.Context) (string, bool) { - token, exists := c.Get("xsrf_token") - if !exists { - return "", false - } - if s, ok := token.(string); ok { - return s, true - } - return "", false -} diff --git a/modules/backend/types.go b/modules/backend/types.go index a069307..20d3158 100644 --- a/modules/backend/types.go +++ b/modules/backend/types.go @@ -1,10 +1,12 @@ package main type Config struct { - Mode string - ServiceAddress string `toml:"ServiceAddress" env:"SERVICE_ADDRESS"` - DdUrl string `toml:"DbUrl" env:"DATABASE_URL"` - JwtPrivateKey string `toml:"JwtPrivateKey" env:"JWT_PRIVATE_KEY"` - LogLevel string `toml:"LogLevel" env:"LOG_LEVEL"` - RmqURL string `toml:"RabbitMQUrl" env:"RABBITMQ_URL"` + Mode string + LogLevel string `toml:"LogLevel" env:"LOG_LEVEL"` +} + +type Item struct { + ID int `json:"id"` + Title string `json:"title"` + Description string `json:"description"` } diff --git a/modules/frontend/package-lock.json b/modules/frontend/package-lock.json index d2b5573..40bb520 100644 --- a/modules/frontend/package-lock.json +++ b/modules/frontend/package-lock.json @@ -13,7 +13,6 @@ "@tailwindcss/vite": "^4.1.17", "axios": "^1.12.2", "react": "^19.1.1", - "react-cookie": "^8.0.1", "react-dom": "^19.1.1", "react-router-dom": "^7.9.4", "tailwindcss": "^4.1.17" @@ -1869,18 +1868,6 @@ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, - "node_modules/@types/hoist-non-react-statics": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.7.tgz", - "integrity": "sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g==", - "license": "MIT", - "dependencies": { - "hoist-non-react-statics": "^3.3.0" - }, - "peerDependencies": { - "@types/react": "*" - } - }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1903,6 +1890,7 @@ "version": "19.2.2", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", + "dev": true, "license": "MIT", "peer": true, "dependencies": { @@ -2536,6 +2524,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, "license": "MIT" }, "node_modules/debug": { @@ -3271,15 +3260,6 @@ "node": ">= 0.4" } }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "license": "BSD-3-Clause", - "dependencies": { - "react-is": "^16.7.0" - } - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4088,20 +4068,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-cookie": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-8.0.1.tgz", - "integrity": "sha512-QNdAd0MLuAiDiLcDU/2s/eyKmmfMHtjPUKJ2dZ/5CcQ9QKUium4B3o61/haq6PQl/YWFqC5PO8GvxeHKhy3GFA==", - "license": "MIT", - "dependencies": { - "@types/hoist-non-react-statics": "^3.3.6", - "hoist-non-react-statics": "^3.3.2", - "universal-cookie": "^8.0.0" - }, - "peerDependencies": { - "react": ">= 16.3.0" - } - }, "node_modules/react-dom": { "version": "19.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", @@ -4115,12 +4081,6 @@ "react": "^19.2.0" } }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" - }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", @@ -4521,15 +4481,6 @@ "devOptional": true, "license": "MIT" }, - "node_modules/universal-cookie": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-8.0.1.tgz", - "integrity": "sha512-B6ks9FLLnP1UbPPcveOidfvB9pHjP+wekP2uRYB9YDfKVpvcjKgy1W5Zj+cEXJ9KTPnqOKGfVDQBmn8/YCQfRg==", - "license": "MIT", - "dependencies": { - "cookie": "^1.0.2" - } - }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", diff --git a/modules/frontend/package.json b/modules/frontend/package.json index af07b41..e0b65ba 100644 --- a/modules/frontend/package.json +++ b/modules/frontend/package.json @@ -15,7 +15,6 @@ "@tailwindcss/vite": "^4.1.17", "axios": "^1.12.2", "react": "^19.1.1", - "react-cookie": "^8.0.1", "react-dom": "^19.1.1", "react-router-dom": "^7.9.4", "tailwindcss": "^4.1.17" diff --git a/modules/frontend/src/App.tsx b/modules/frontend/src/App.tsx index 67336c1..95b59e3 100644 --- a/modules/frontend/src/App.tsx +++ b/modules/frontend/src/App.tsx @@ -6,11 +6,6 @@ import TitlePage from "./pages/TitlePage/TitlePage"; import { LoginPage } from "./pages/LoginPage/LoginPage"; import { Header } from "./components/Header/Header"; -import { OpenAPI } from "./api"; - -OpenAPI.WITH_CREDENTIALS = true -OpenAPI.CREDENTIALS = 'include' - const App: React.FC = () => { const username = localStorage.getItem("username") || undefined; const userId = localStorage.getItem("userId"); diff --git a/modules/frontend/src/api/services/DefaultService.ts b/modules/frontend/src/api/services/DefaultService.ts index 6898c46..218b461 100644 --- a/modules/frontend/src/api/services/DefaultService.ts +++ b/modules/frontend/src/api/services/DefaultService.ts @@ -20,7 +20,6 @@ export class DefaultService { * @param cursor * @param sort * @param sortForward - * @param extSearch * @param word * @param status List of title statuses to filter * @param rating @@ -36,7 +35,6 @@ export class DefaultService { cursor?: string, sort?: TitleSort, sortForward: boolean = true, - extSearch: boolean = false, word?: string, status?: Array, rating?: number, @@ -59,7 +57,6 @@ export class DefaultService { 'cursor': cursor, 'sort': sort, 'sort_forward': sortForward, - 'ext_search': extSearch, 'word': word, 'status': status, 'rating': rating, diff --git a/modules/frontend/src/auth/services/AuthService.ts b/modules/frontend/src/auth/services/AuthService.ts index 74a8fa7..94578d8 100644 --- a/modules/frontend/src/auth/services/AuthService.ts +++ b/modules/frontend/src/auth/services/AuthService.ts @@ -12,17 +12,19 @@ export class AuthService { * @returns any Sign-up result * @throws ApiError */ - public static postSignUp( + public static postAuthSignUp( requestBody: { nickname: string; pass: string; }, ): CancelablePromise<{ - user_id: number; + success?: boolean; + error?: string | null; + user_id?: string | null; }> { return __request(OpenAPI, { method: 'POST', - url: '/sign-up', + url: '/auth/sign-up', body: requestBody, mediaType: 'application/json', }); @@ -33,18 +35,19 @@ export class AuthService { * @returns any Sign-in result with JWT * @throws ApiError */ - public static postSignIn( + public static postAuthSignIn( requestBody: { nickname: string; pass: string; }, ): CancelablePromise<{ - user_id: number; - user_name: string; + error?: string | null; + user_id?: string | null; + user_name?: string | null; }> { return __request(OpenAPI, { method: 'POST', - url: '/sign-in', + url: '/auth/sign-in', body: requestBody, mediaType: 'application/json', errors: { diff --git a/modules/frontend/src/components/TitleStatusControls/TitleStatusControls.tsx b/modules/frontend/src/components/TitleStatusControls/TitleStatusControls.tsx index cc9f80d..0c9c741 100644 --- a/modules/frontend/src/components/TitleStatusControls/TitleStatusControls.tsx +++ b/modules/frontend/src/components/TitleStatusControls/TitleStatusControls.tsx @@ -1,8 +1,6 @@ import { useEffect, useState } from "react"; import { DefaultService } from "../../api"; import type { UserTitleStatus } from "../../api"; -// import { useCookies } from 'react-cookie'; - import { ClockIcon, CheckCircleIcon, @@ -19,9 +17,6 @@ const STATUS_BUTTONS: { status: UserTitleStatus; icon: React.ReactNode; label: s ]; export function TitleStatusControls({ titleId }: { titleId: number }) { - // const [cookies] = useCookies(['xsrf_token']); - // const xsrfToken = cookies['xsrf_token'] || null; - const [currentStatus, setCurrentStatus] = useState(null); const [loading, setLoading] = useState(false); diff --git a/modules/frontend/src/components/TitlesFilterPanel/TitlesFilterPanel.tsx b/modules/frontend/src/components/TitlesFilterPanel/TitlesFilterPanel.tsx deleted file mode 100644 index 3cfef69..0000000 --- a/modules/frontend/src/components/TitlesFilterPanel/TitlesFilterPanel.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import { useState } from "react"; -import type { TitleStatus, ReleaseSeason } from "../../api"; -import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/react/24/solid"; - -export type TitlesFilter = { - extSearch: boolean; - status: TitleStatus | ""; - rating: number | ""; - releaseYear: number | ""; - releaseSeason: ReleaseSeason | ""; -}; - -type TitlesFilterPanelProps = { - filters: TitlesFilter; - setFilters: (filters: TitlesFilter) => void; -}; - -const STATUS_OPTIONS: (TitleStatus | "")[] = ["", "planned", "finished", "ongoing"]; -const SEASON_OPTIONS: (ReleaseSeason | "")[] = ["", "winter", "spring", "summer", "fall"]; -const RATING_OPTIONS = ["", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - -export function TitlesFilterPanel({ filters, setFilters }: TitlesFilterPanelProps) { - const [open, setOpen] = useState(false); - - const handleChange = (field: keyof TitlesFilter, value: any) => { - setFilters({ ...filters, [field]: value }); - }; - - return ( -
-
- {/* Заголовок панели */} -
setOpen((prev) => !prev)} - > -

Filters

- {open ? : } -
- - {/* Контент панели */} - {open && ( -
- {/* Extended Search */} -
- handleChange("extSearch", e.target.checked)} - className="w-4 h-4" - /> - -
- - {/* Status */} -
- - -
- - {/* Rating */} -
- - -
- - {/* Release Year */} -
- - - handleChange("releaseYear", e.target.value ? Number(e.target.value) : "") - } - className="border rounded px-2 py-1" - placeholder="Any" - /> -
- - {/* Release Season */} -
- - -
-
- )} -
-
- ); -} diff --git a/modules/frontend/src/main.tsx b/modules/frontend/src/main.tsx index c225a33..bef5202 100644 --- a/modules/frontend/src/main.tsx +++ b/modules/frontend/src/main.tsx @@ -1,13 +1,10 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' -import { CookiesProvider } from 'react-cookie' import './index.css' import App from './App.tsx' createRoot(document.getElementById('root')!).render( - - - + , ) diff --git a/modules/frontend/src/pages/LoginPage/LoginPage.tsx b/modules/frontend/src/pages/LoginPage/LoginPage.tsx index 928766e..89ee88c 100644 --- a/modules/frontend/src/pages/LoginPage/LoginPage.tsx +++ b/modules/frontend/src/pages/LoginPage/LoginPage.tsx @@ -17,23 +17,23 @@ export const LoginPage: React.FC = () => { try { if (isLogin) { - const res = await AuthService.postSignIn({ nickname, pass: password }); + const res = await AuthService.postAuthSignIn({ nickname, pass: password }); if (res.user_id && res.user_name) { // Сохраняем user_id и username в localStorage - localStorage.setItem("userId", res.user_id.toString()); + localStorage.setItem("userId", res.user_id); localStorage.setItem("username", res.user_name); navigate("/profile"); // редирект на профиль } else { - setError("Login failed"); + setError(res.error || "Login failed"); } } else { // SignUp оставляем без сохранения данных - const res = await AuthService.postSignUp({ nickname, pass: password }); + const res = await AuthService.postAuthSignUp({ nickname, pass: password }); if (res.user_id) { setIsLogin(true); // переключаемся на login после регистрации } else { - setError("Sign up failed"); + setError(res.error || "Sign up failed"); } } } catch (err: any) { diff --git a/modules/frontend/src/pages/TitlesPage/TitlesPage.tsx b/modules/frontend/src/pages/TitlesPage/TitlesPage.tsx index ed55d8d..c9911b9 100644 --- a/modules/frontend/src/pages/TitlesPage/TitlesPage.tsx +++ b/modules/frontend/src/pages/TitlesPage/TitlesPage.tsx @@ -8,7 +8,6 @@ import { TitleCardHorizontal } from "../../components/cards/TitleCardHorizontal" import type { CursorObj, Title, TitleSort } from "../../api"; import { LayoutSwitch } from "../../components/LayoutSwitch/LayoutSwitch"; import { Link } from "react-router-dom"; -import { type TitlesFilter, TitlesFilterPanel } from "../../components/TitlesFilterPanel/TitlesFilterPanel"; const PAGE_SIZE = 10; @@ -23,14 +22,6 @@ export default function TitlesPage() { const [sortForward, setSortForward] = useState(true); const [layout, setLayout] = useState<"square" | "horizontal">("square"); - const [filters, setFilters] = useState({ - extSearch: false, - status: "", - rating: "", - releaseYear: "", - releaseSeason: "", - }); - const fetchPage = async (cursorObj: CursorObj | null) => { const cursorStr = cursorObj ? btoa(JSON.stringify(cursorObj)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') : ""; @@ -39,14 +30,13 @@ export default function TitlesPage() { cursorStr, sort, sortForward, - filters.extSearch, search.trim() || undefined, - filters.status ? [filters.status] : undefined, - filters.rating || undefined, - filters.releaseYear || undefined, - filters.releaseSeason || undefined, - PAGE_SIZE, + undefined, + undefined, + undefined, + undefined, PAGE_SIZE, + undefined, "all" ); @@ -83,7 +73,7 @@ export default function TitlesPage() { }; initLoad(); - }, [search, sort, sortForward, filters]); + }, [search, sort, sortForward]); const handleLoadMore = async () => { @@ -131,7 +121,6 @@ const handleLoadMore = async () => { setSortForward={setSortForward} /> - {loading &&
Loading...
}