diff --git a/.forgejo/workflows/build-and-deploy.yml b/.forgejo/workflows/build-and-deploy.yml index e7d0a83..87a8b34 100644 --- a/.forgejo/workflows/build-and-deploy.yml +++ b/.forgejo/workflows/build-and-deploy.yml @@ -5,7 +5,6 @@ on: branches: - master - cicd - - dev jobs: build: @@ -13,54 +12,31 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 - - # Build backend + - uses: actions/setup-go@v6 with: go-version: '^1.25' check-latest: false cache-dependency-path: | - modules/backend/go.sum - - - name: Build Go app - run: | - cd modules/backend - go mod tidy - go build -o nyanimedb . - tar -czvf nyanimedb-backend.tar.gz nyanimedb + modules/server/go.sum - - name: Upload built backend to artifactory - uses: actions/upload-artifact@v3 - with: - name: nyanimedb-backend.tar.gz - path: modules/backend/nyanimedb-backend.tar.gz - - # Build frontend - - uses: actions/setup-node@v5 - with: - node-version-file: modules/frontend/package.json - cache: npm - cache-dependency-path: modules/frontend/package-lock.json - - - name: Build frontend - env: - VITE_BACKEND_API_BASE_URL: ${{ vars.BACKEND_API_BASE_URL }} - run: | - cd modules/frontend - npm install - npm run build - tar -czvf nyanimedb-frontend.tar.gz dist/ - - - name: Upload built frontend to artifactory - uses: actions/upload-artifact@v3 - with: - name: nyanimedb-frontend.tar.gz - path: modules/frontend/nyanimedb-frontend.tar.gz - - # Build Docker images - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + # Build application + - name: Build Go app + run: | + cd modules/server + go mod tidy + go build -o nyanimedb . + + - name: Upload built application to artifactory + uses: actions/upload-artifact@v3 + with: + name: nyanimedb + path: modules/server/nyanimedb + + # Build Docker image - name: Login to Docker Hub uses: docker/login-action@v3 with: @@ -68,21 +44,13 @@ jobs: username: ${{ secrets.REGISTRY_USERNAME }} password: ${{ secrets.REGISTRY_TOKEN }} - - name: Build and push backend image + - name: Build and push uses: docker/build-push-action@v6 with: - context: . - file: Dockerfiles/Dockerfile_backend + context: ./modules/server + file: Dockerfiles/Dockerfile_server push: true - tags: meowgit.nekoea.red/nihonium/nyanimedb-backend:latest - - - name: Build and push frontend image - uses: docker/build-push-action@v6 - with: - context: . - file: Dockerfiles/Dockerfile_frontend - push: true - tags: meowgit.nekoea.red/nihonium/nyanimedb-frontend:latest + tags: meowgit.nekoea.red/nihonium/nyanimedb:latest deploy: runs-on: self-hosted @@ -94,7 +62,6 @@ jobs: POSTGRES_PORT: 5432 POSTGRES_VERSION: 18 LOG_LEVEL: ${{ vars.LOG_LEVEL }} - DATABASE_URL: ${{ secrets.DATABASE_URL }} steps: - name: Checkout code @@ -104,5 +71,4 @@ jobs: run: | cd deploy docker compose down || true - docker compose pull || true docker compose up -d diff --git a/Dockerfiles/Dockerfile_backend b/Dockerfiles/Dockerfile_backend deleted file mode 100644 index 68c2414..0000000 --- a/Dockerfiles/Dockerfile_backend +++ /dev/null @@ -1,6 +0,0 @@ -FROM ubuntu:22.04 - -WORKDIR /app -COPY --chmod=755 modules/backend/nyanimedb /app -EXPOSE 8080 -ENTRYPOINT ["/app/nyanimedb"] \ No newline at end of file diff --git a/Dockerfiles/Dockerfile_frontend b/Dockerfiles/Dockerfile_frontend index 18bc6d7..9f0752e 100644 --- a/Dockerfiles/Dockerfile_frontend +++ b/Dockerfiles/Dockerfile_frontend @@ -1,5 +1,13 @@ +FROM node:20-alpine AS builder +ARG VITE_BACKEND_API_BASE_URL +ENV VITE_BACKEND_API_BASE_URL=$VITE_BACKEND_API_BASE_URL +WORKDIR /app +COPY modules/frontend/ ./ +RUN echo "VITE_BACKEND_API_BASE_URL=$VITE_BACKEND_API_BASE_URL" +RUN npm install +RUN npm run build + FROM nginx:alpine -COPY modules/frontend/dist /usr/share/nginx/html -COPY modules/frontend/nginx-default.conf /etc/nginx/conf.d/default.conf +COPY --from=builder /app/dist /usr/share/nginx/html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/Dockerfiles/Dockerfile_server b/Dockerfiles/Dockerfile_server new file mode 100644 index 0000000..87014ce --- /dev/null +++ b/Dockerfiles/Dockerfile_server @@ -0,0 +1,20 @@ +FROM ubuntu:22.04 + +WORKDIR /app +COPY --chmod=755 nyanimedb /app +COPY templates /app/templates +EXPOSE 8080 +ENTRYPOINT ["/app/nyanimedb"] + +# FROM golang:1.25 AS builder + +# ARG VERSION=dev + +# WORKDIR /go/src/app +# COPY main.go . +# RUN go build -o main -ldflags=-X=main.version=${VERSION} main.go + +# FROM debian:buster-slim +# COPY --from=builder /go/src/app/main /go/bin/main +# ENV PATH="/go/bin:${PATH}" +# CMD ["main"] \ No newline at end of file diff --git a/api/api.gen.go b/api/api.gen.go deleted file mode 100644 index 427f5af..0000000 --- a/api/api.gen.go +++ /dev/null @@ -1,1213 +0,0 @@ -// Package oapi provides primitives to interact with the openapi HTTP API. -// -// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.5.0 DO NOT EDIT. -package oapi - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/gin-gonic/gin" - "github.com/oapi-codegen/runtime" - strictgin "github.com/oapi-codegen/runtime/strictmiddleware/gin" - openapi_types "github.com/oapi-codegen/runtime/types" -) - -// Defines values for ReleaseSeason. -const ( - Fall ReleaseSeason = "fall" - Spring ReleaseSeason = "spring" - Summer ReleaseSeason = "summer" - Winter ReleaseSeason = "winter" -) - -// Defines values for TitleSort. -const ( - Id TitleSort = "id" - Rating TitleSort = "rating" - Views TitleSort = "views" - Year TitleSort = "year" -) - -// Defines values for TitleStatus. -const ( - TitleStatusFinished TitleStatus = "finished" - TitleStatusOngoing TitleStatus = "ongoing" - TitleStatusPlanned TitleStatus = "planned" -) - -// Defines values for UserTitleStatus. -const ( - UserTitleStatusDropped UserTitleStatus = "dropped" - UserTitleStatusFinished UserTitleStatus = "finished" - UserTitleStatusInProgress UserTitleStatus = "in-progress" - UserTitleStatusPlanned UserTitleStatus = "planned" -) - -// Image defines model for Image. -type Image struct { - Id *int64 `json:"id,omitempty"` - ImagePath *string `json:"image_path,omitempty"` - StorageType *string `json:"storage_type,omitempty"` -} - -// ReleaseSeason Title release season -type ReleaseSeason string - -// Studio defines model for Studio. -type Studio struct { - Description *string `json:"description,omitempty"` - Id int64 `json:"id"` - Name string `json:"name"` - Poster *Image `json:"poster,omitempty"` -} - -// Tag A localized tag: keys are language codes (ISO 639-1), values are tag names -type Tag map[string]string - -// Tags Array of localized tags -type Tags = []Tag - -// Title defines model for Title. -type Title struct { - EpisodesAired *int32 `json:"episodes_aired,omitempty"` - EpisodesAll *int32 `json:"episodes_all,omitempty"` - EpisodesLen *map[string]float64 `json:"episodes_len,omitempty"` - - // Id Unique title ID (primary key) - Id int64 `json:"id"` - Poster *Image `json:"poster,omitempty"` - Rating *float64 `json:"rating,omitempty"` - RatingCount *int32 `json:"rating_count,omitempty"` - - // ReleaseSeason Title release season - ReleaseSeason *ReleaseSeason `json:"release_season,omitempty"` - ReleaseYear *int32 `json:"release_year,omitempty"` - Studio *Studio `json:"studio,omitempty"` - - // Tags Array of localized tags - Tags Tags `json:"tags"` - - // TitleNames Localized titles. Key = language (ISO 639-1), value = list of names - TitleNames map[string][]string `json:"title_names"` - - // TitleStatus Title status - TitleStatus *TitleStatus `json:"title_status,omitempty"` - AdditionalProperties map[string]interface{} `json:"-"` -} - -// TitleSort Title sort order -type TitleSort string - -// TitleStatus Title status -type TitleStatus string - -// User defines model for User. -type User struct { - // AvatarId ID of the user avatar (references images table) - AvatarId *int64 `json:"avatar_id"` - - // CreationDate Timestamp when the user was created - CreationDate *time.Time `json:"creation_date,omitempty"` - - // DispName Display name - DispName *string `json:"disp_name,omitempty"` - - // Id Unique user ID (primary key) - Id *int64 `json:"id,omitempty"` - - // Mail User email - Mail *openapi_types.Email `json:"mail,omitempty"` - - // Nickname Username (alphanumeric + _ or -) - Nickname string `json:"nickname"` - - // UserDesc User description - UserDesc *string `json:"user_desc,omitempty"` -} - -// UserTitle defines model for UserTitle. -type UserTitle struct { - Ctime *time.Time `json:"ctime,omitempty"` - Rate *int32 `json:"rate,omitempty"` - ReviewId *int64 `json:"review_id,omitempty"` - - // Status User's title status - Status UserTitleStatus `json:"status"` - TitleId int64 `json:"title_id"` - UserId int64 `json:"user_id"` - AdditionalProperties map[string]interface{} `json:"-"` -} - -// UserTitleStatus User's title status -type UserTitleStatus string - -// Cursor defines model for cursor. -type Cursor = string - -// GetTitlesParams defines parameters for GetTitles. -type GetTitlesParams struct { - Cursor *Cursor `form:"cursor,omitempty" json:"cursor,omitempty"` - Sort *TitleSort `form:"sort,omitempty" json:"sort,omitempty"` - SortForward *bool `form:"sort_forward,omitempty" json:"sort_forward,omitempty"` - Word *string `form:"word,omitempty" json:"word,omitempty"` - Status *TitleStatus `form:"status,omitempty" json:"status,omitempty"` - Rating *float64 `form:"rating,omitempty" json:"rating,omitempty"` - ReleaseYear *int32 `form:"release_year,omitempty" json:"release_year,omitempty"` - ReleaseSeason *ReleaseSeason `form:"release_season,omitempty" json:"release_season,omitempty"` - Limit *int32 `form:"limit,omitempty" json:"limit,omitempty"` - Offset *int32 `form:"offset,omitempty" json:"offset,omitempty"` - Fields *string `form:"fields,omitempty" json:"fields,omitempty"` -} - -// GetTitlesTitleIdParams defines parameters for GetTitlesTitleId. -type GetTitlesTitleIdParams struct { - Fields *string `form:"fields,omitempty" json:"fields,omitempty"` -} - -// GetUsersUserIdParams defines parameters for GetUsersUserId. -type GetUsersUserIdParams struct { - Fields *string `form:"fields,omitempty" json:"fields,omitempty"` -} - -// GetUsersUserIdTitlesParams defines parameters for GetUsersUserIdTitles. -type GetUsersUserIdTitlesParams struct { - Cursor *Cursor `form:"cursor,omitempty" json:"cursor,omitempty"` - Word *string `form:"word,omitempty" json:"word,omitempty"` - Status *TitleStatus `form:"status,omitempty" json:"status,omitempty"` - WatchStatus *UserTitleStatus `form:"watch_status,omitempty" json:"watch_status,omitempty"` - Rating *float64 `form:"rating,omitempty" json:"rating,omitempty"` - ReleaseYear *int32 `form:"release_year,omitempty" json:"release_year,omitempty"` - ReleaseSeason *ReleaseSeason `form:"release_season,omitempty" json:"release_season,omitempty"` - Limit *int32 `form:"limit,omitempty" json:"limit,omitempty"` - Fields *string `form:"fields,omitempty" json:"fields,omitempty"` -} - -// Getter for additional properties for Title. Returns the specified -// element and whether it was found -func (a Title) Get(fieldName string) (value interface{}, found bool) { - if a.AdditionalProperties != nil { - value, found = a.AdditionalProperties[fieldName] - } - return -} - -// Setter for additional properties for Title -func (a *Title) Set(fieldName string, value interface{}) { - if a.AdditionalProperties == nil { - a.AdditionalProperties = make(map[string]interface{}) - } - a.AdditionalProperties[fieldName] = value -} - -// Override default JSON handling for Title to handle AdditionalProperties -func (a *Title) UnmarshalJSON(b []byte) error { - object := make(map[string]json.RawMessage) - err := json.Unmarshal(b, &object) - if err != nil { - return err - } - - if raw, found := object["episodes_aired"]; found { - err = json.Unmarshal(raw, &a.EpisodesAired) - if err != nil { - return fmt.Errorf("error reading 'episodes_aired': %w", err) - } - delete(object, "episodes_aired") - } - - if raw, found := object["episodes_all"]; found { - err = json.Unmarshal(raw, &a.EpisodesAll) - if err != nil { - return fmt.Errorf("error reading 'episodes_all': %w", err) - } - delete(object, "episodes_all") - } - - if raw, found := object["episodes_len"]; found { - err = json.Unmarshal(raw, &a.EpisodesLen) - if err != nil { - return fmt.Errorf("error reading 'episodes_len': %w", err) - } - delete(object, "episodes_len") - } - - if raw, found := object["id"]; found { - err = json.Unmarshal(raw, &a.Id) - if err != nil { - return fmt.Errorf("error reading 'id': %w", err) - } - delete(object, "id") - } - - if raw, found := object["poster"]; found { - err = json.Unmarshal(raw, &a.Poster) - if err != nil { - return fmt.Errorf("error reading 'poster': %w", err) - } - delete(object, "poster") - } - - if raw, found := object["rating"]; found { - err = json.Unmarshal(raw, &a.Rating) - if err != nil { - return fmt.Errorf("error reading 'rating': %w", err) - } - delete(object, "rating") - } - - if raw, found := object["rating_count"]; found { - err = json.Unmarshal(raw, &a.RatingCount) - if err != nil { - return fmt.Errorf("error reading 'rating_count': %w", err) - } - delete(object, "rating_count") - } - - if raw, found := object["release_season"]; found { - err = json.Unmarshal(raw, &a.ReleaseSeason) - if err != nil { - return fmt.Errorf("error reading 'release_season': %w", err) - } - delete(object, "release_season") - } - - if raw, found := object["release_year"]; found { - err = json.Unmarshal(raw, &a.ReleaseYear) - if err != nil { - return fmt.Errorf("error reading 'release_year': %w", err) - } - delete(object, "release_year") - } - - if raw, found := object["studio"]; found { - err = json.Unmarshal(raw, &a.Studio) - if err != nil { - return fmt.Errorf("error reading 'studio': %w", err) - } - delete(object, "studio") - } - - if raw, found := object["tags"]; found { - err = json.Unmarshal(raw, &a.Tags) - if err != nil { - return fmt.Errorf("error reading 'tags': %w", err) - } - delete(object, "tags") - } - - if raw, found := object["title_names"]; found { - err = json.Unmarshal(raw, &a.TitleNames) - if err != nil { - return fmt.Errorf("error reading 'title_names': %w", err) - } - delete(object, "title_names") - } - - if raw, found := object["title_status"]; found { - err = json.Unmarshal(raw, &a.TitleStatus) - if err != nil { - return fmt.Errorf("error reading 'title_status': %w", err) - } - delete(object, "title_status") - } - - if len(object) != 0 { - a.AdditionalProperties = make(map[string]interface{}) - for fieldName, fieldBuf := range object { - var fieldVal interface{} - err := json.Unmarshal(fieldBuf, &fieldVal) - if err != nil { - return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) - } - a.AdditionalProperties[fieldName] = fieldVal - } - } - return nil -} - -// Override default JSON handling for Title to handle AdditionalProperties -func (a Title) MarshalJSON() ([]byte, error) { - var err error - object := make(map[string]json.RawMessage) - - if a.EpisodesAired != nil { - object["episodes_aired"], err = json.Marshal(a.EpisodesAired) - if err != nil { - return nil, fmt.Errorf("error marshaling 'episodes_aired': %w", err) - } - } - - if a.EpisodesAll != nil { - object["episodes_all"], err = json.Marshal(a.EpisodesAll) - if err != nil { - return nil, fmt.Errorf("error marshaling 'episodes_all': %w", err) - } - } - - if a.EpisodesLen != nil { - object["episodes_len"], err = json.Marshal(a.EpisodesLen) - if err != nil { - return nil, fmt.Errorf("error marshaling 'episodes_len': %w", err) - } - } - - object["id"], err = json.Marshal(a.Id) - if err != nil { - return nil, fmt.Errorf("error marshaling 'id': %w", err) - } - - if a.Poster != nil { - object["poster"], err = json.Marshal(a.Poster) - if err != nil { - return nil, fmt.Errorf("error marshaling 'poster': %w", err) - } - } - - if a.Rating != nil { - object["rating"], err = json.Marshal(a.Rating) - if err != nil { - return nil, fmt.Errorf("error marshaling 'rating': %w", err) - } - } - - if a.RatingCount != nil { - object["rating_count"], err = json.Marshal(a.RatingCount) - if err != nil { - return nil, fmt.Errorf("error marshaling 'rating_count': %w", err) - } - } - - if a.ReleaseSeason != nil { - object["release_season"], err = json.Marshal(a.ReleaseSeason) - if err != nil { - return nil, fmt.Errorf("error marshaling 'release_season': %w", err) - } - } - - if a.ReleaseYear != nil { - object["release_year"], err = json.Marshal(a.ReleaseYear) - if err != nil { - return nil, fmt.Errorf("error marshaling 'release_year': %w", err) - } - } - - if a.Studio != nil { - object["studio"], err = json.Marshal(a.Studio) - if err != nil { - return nil, fmt.Errorf("error marshaling 'studio': %w", err) - } - } - - object["tags"], err = json.Marshal(a.Tags) - if err != nil { - return nil, fmt.Errorf("error marshaling 'tags': %w", err) - } - - object["title_names"], err = json.Marshal(a.TitleNames) - if err != nil { - return nil, fmt.Errorf("error marshaling 'title_names': %w", err) - } - - if a.TitleStatus != nil { - object["title_status"], err = json.Marshal(a.TitleStatus) - if err != nil { - return nil, fmt.Errorf("error marshaling 'title_status': %w", err) - } - } - - for fieldName, field := range a.AdditionalProperties { - object[fieldName], err = json.Marshal(field) - if err != nil { - return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err) - } - } - return json.Marshal(object) -} - -// Getter for additional properties for UserTitle. Returns the specified -// element and whether it was found -func (a UserTitle) Get(fieldName string) (value interface{}, found bool) { - if a.AdditionalProperties != nil { - value, found = a.AdditionalProperties[fieldName] - } - return -} - -// Setter for additional properties for UserTitle -func (a *UserTitle) Set(fieldName string, value interface{}) { - if a.AdditionalProperties == nil { - a.AdditionalProperties = make(map[string]interface{}) - } - a.AdditionalProperties[fieldName] = value -} - -// Override default JSON handling for UserTitle to handle AdditionalProperties -func (a *UserTitle) UnmarshalJSON(b []byte) error { - object := make(map[string]json.RawMessage) - err := json.Unmarshal(b, &object) - if err != nil { - return err - } - - if raw, found := object["ctime"]; found { - err = json.Unmarshal(raw, &a.Ctime) - if err != nil { - return fmt.Errorf("error reading 'ctime': %w", err) - } - delete(object, "ctime") - } - - if raw, found := object["rate"]; found { - err = json.Unmarshal(raw, &a.Rate) - if err != nil { - return fmt.Errorf("error reading 'rate': %w", err) - } - delete(object, "rate") - } - - if raw, found := object["review_id"]; found { - err = json.Unmarshal(raw, &a.ReviewId) - if err != nil { - return fmt.Errorf("error reading 'review_id': %w", err) - } - delete(object, "review_id") - } - - if raw, found := object["status"]; found { - err = json.Unmarshal(raw, &a.Status) - if err != nil { - return fmt.Errorf("error reading 'status': %w", err) - } - delete(object, "status") - } - - if raw, found := object["title_id"]; found { - err = json.Unmarshal(raw, &a.TitleId) - if err != nil { - return fmt.Errorf("error reading 'title_id': %w", err) - } - delete(object, "title_id") - } - - if raw, found := object["user_id"]; found { - err = json.Unmarshal(raw, &a.UserId) - if err != nil { - return fmt.Errorf("error reading 'user_id': %w", err) - } - delete(object, "user_id") - } - - if len(object) != 0 { - a.AdditionalProperties = make(map[string]interface{}) - for fieldName, fieldBuf := range object { - var fieldVal interface{} - err := json.Unmarshal(fieldBuf, &fieldVal) - if err != nil { - return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) - } - a.AdditionalProperties[fieldName] = fieldVal - } - } - return nil -} - -// Override default JSON handling for UserTitle to handle AdditionalProperties -func (a UserTitle) MarshalJSON() ([]byte, error) { - var err error - object := make(map[string]json.RawMessage) - - if a.Ctime != nil { - object["ctime"], err = json.Marshal(a.Ctime) - if err != nil { - return nil, fmt.Errorf("error marshaling 'ctime': %w", err) - } - } - - if a.Rate != nil { - object["rate"], err = json.Marshal(a.Rate) - if err != nil { - return nil, fmt.Errorf("error marshaling 'rate': %w", err) - } - } - - if a.ReviewId != nil { - object["review_id"], err = json.Marshal(a.ReviewId) - if err != nil { - return nil, fmt.Errorf("error marshaling 'review_id': %w", err) - } - } - - object["status"], err = json.Marshal(a.Status) - if err != nil { - return nil, fmt.Errorf("error marshaling 'status': %w", err) - } - - object["title_id"], err = json.Marshal(a.TitleId) - if err != nil { - return nil, fmt.Errorf("error marshaling 'title_id': %w", err) - } - - object["user_id"], err = json.Marshal(a.UserId) - if err != nil { - return nil, fmt.Errorf("error marshaling 'user_id': %w", err) - } - - for fieldName, field := range a.AdditionalProperties { - object[fieldName], err = json.Marshal(field) - if err != nil { - return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err) - } - } - return json.Marshal(object) -} - -// ServerInterface represents all server handlers. -type ServerInterface interface { - // Get titles - // (GET /titles) - GetTitles(c *gin.Context, params GetTitlesParams) - // Get title description - // (GET /titles/{title_id}) - GetTitlesTitleId(c *gin.Context, titleId int64, params GetTitlesTitleIdParams) - // Get user info - // (GET /users/{user_id}) - GetUsersUserId(c *gin.Context, userId string, params GetUsersUserIdParams) - // Get user titles - // (GET /users/{user_id}/titles/) - GetUsersUserIdTitles(c *gin.Context, userId string, params GetUsersUserIdTitlesParams) -} - -// ServerInterfaceWrapper converts contexts to parameters. -type ServerInterfaceWrapper struct { - Handler ServerInterface - HandlerMiddlewares []MiddlewareFunc - ErrorHandler func(*gin.Context, error, int) -} - -type MiddlewareFunc func(c *gin.Context) - -// GetTitles operation middleware -func (siw *ServerInterfaceWrapper) GetTitles(c *gin.Context) { - - var err error - - // Parameter object where we will unmarshal all parameters from the context - var params GetTitlesParams - - // ------------- Optional query parameter "cursor" ------------- - - err = runtime.BindQueryParameter("form", true, false, "cursor", c.Request.URL.Query(), ¶ms.Cursor) - if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter cursor: %w", err), http.StatusBadRequest) - return - } - - // ------------- Optional query parameter "sort" ------------- - - err = runtime.BindQueryParameter("form", true, false, "sort", c.Request.URL.Query(), ¶ms.Sort) - if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter sort: %w", err), http.StatusBadRequest) - return - } - - // ------------- Optional query parameter "sort_forward" ------------- - - err = runtime.BindQueryParameter("form", true, false, "sort_forward", c.Request.URL.Query(), ¶ms.SortForward) - if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter sort_forward: %w", err), http.StatusBadRequest) - return - } - - // ------------- Optional query parameter "word" ------------- - - err = runtime.BindQueryParameter("form", true, false, "word", c.Request.URL.Query(), ¶ms.Word) - if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter word: %w", err), http.StatusBadRequest) - return - } - - // ------------- Optional query parameter "status" ------------- - - err = runtime.BindQueryParameter("form", true, false, "status", c.Request.URL.Query(), ¶ms.Status) - if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter status: %w", err), http.StatusBadRequest) - return - } - - // ------------- Optional query parameter "rating" ------------- - - err = runtime.BindQueryParameter("form", true, false, "rating", c.Request.URL.Query(), ¶ms.Rating) - if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter rating: %w", err), http.StatusBadRequest) - return - } - - // ------------- Optional query parameter "release_year" ------------- - - err = runtime.BindQueryParameter("form", true, false, "release_year", c.Request.URL.Query(), ¶ms.ReleaseYear) - if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter release_year: %w", err), http.StatusBadRequest) - return - } - - // ------------- Optional query parameter "release_season" ------------- - - err = runtime.BindQueryParameter("form", true, false, "release_season", c.Request.URL.Query(), ¶ms.ReleaseSeason) - if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter release_season: %w", err), http.StatusBadRequest) - return - } - - // ------------- Optional query parameter "limit" ------------- - - err = runtime.BindQueryParameter("form", true, false, "limit", c.Request.URL.Query(), ¶ms.Limit) - if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter limit: %w", err), http.StatusBadRequest) - return - } - - // ------------- Optional query parameter "offset" ------------- - - err = runtime.BindQueryParameter("form", true, false, "offset", c.Request.URL.Query(), ¶ms.Offset) - if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter offset: %w", err), http.StatusBadRequest) - return - } - - // ------------- Optional query parameter "fields" ------------- - - err = runtime.BindQueryParameter("form", true, false, "fields", c.Request.URL.Query(), ¶ms.Fields) - if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter fields: %w", err), http.StatusBadRequest) - return - } - - for _, middleware := range siw.HandlerMiddlewares { - middleware(c) - if c.IsAborted() { - return - } - } - - siw.Handler.GetTitles(c, params) -} - -// GetTitlesTitleId operation middleware -func (siw *ServerInterfaceWrapper) GetTitlesTitleId(c *gin.Context) { - - var err error - - // ------------- Path parameter "title_id" ------------- - var titleId int64 - - err = runtime.BindStyledParameterWithOptions("simple", "title_id", c.Param("title_id"), &titleId, runtime.BindStyledParameterOptions{Explode: false, Required: true}) - if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter title_id: %w", err), http.StatusBadRequest) - return - } - - // Parameter object where we will unmarshal all parameters from the context - var params GetTitlesTitleIdParams - - // ------------- Optional query parameter "fields" ------------- - - err = runtime.BindQueryParameter("form", true, false, "fields", c.Request.URL.Query(), ¶ms.Fields) - if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter fields: %w", err), http.StatusBadRequest) - return - } - - for _, middleware := range siw.HandlerMiddlewares { - middleware(c) - if c.IsAborted() { - return - } - } - - siw.Handler.GetTitlesTitleId(c, titleId, params) -} - -// GetUsersUserId operation middleware -func (siw *ServerInterfaceWrapper) GetUsersUserId(c *gin.Context) { - - var err error - - // ------------- Path parameter "user_id" ------------- - var userId string - - err = runtime.BindStyledParameterWithOptions("simple", "user_id", c.Param("user_id"), &userId, runtime.BindStyledParameterOptions{Explode: false, Required: true}) - if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter user_id: %w", err), http.StatusBadRequest) - return - } - - // Parameter object where we will unmarshal all parameters from the context - var params GetUsersUserIdParams - - // ------------- Optional query parameter "fields" ------------- - - err = runtime.BindQueryParameter("form", true, false, "fields", c.Request.URL.Query(), ¶ms.Fields) - if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter fields: %w", err), http.StatusBadRequest) - return - } - - for _, middleware := range siw.HandlerMiddlewares { - middleware(c) - if c.IsAborted() { - return - } - } - - siw.Handler.GetUsersUserId(c, userId, params) -} - -// GetUsersUserIdTitles operation middleware -func (siw *ServerInterfaceWrapper) GetUsersUserIdTitles(c *gin.Context) { - - var err error - - // ------------- Path parameter "user_id" ------------- - var userId string - - err = runtime.BindStyledParameterWithOptions("simple", "user_id", c.Param("user_id"), &userId, runtime.BindStyledParameterOptions{Explode: false, Required: true}) - if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter user_id: %w", err), http.StatusBadRequest) - return - } - - // Parameter object where we will unmarshal all parameters from the context - var params GetUsersUserIdTitlesParams - - // ------------- Optional query parameter "cursor" ------------- - - err = runtime.BindQueryParameter("form", true, false, "cursor", c.Request.URL.Query(), ¶ms.Cursor) - if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter cursor: %w", err), http.StatusBadRequest) - return - } - - // ------------- Optional query parameter "word" ------------- - - err = runtime.BindQueryParameter("form", true, false, "word", c.Request.URL.Query(), ¶ms.Word) - if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter word: %w", err), http.StatusBadRequest) - return - } - - // ------------- Optional query parameter "status" ------------- - - err = runtime.BindQueryParameter("form", true, false, "status", c.Request.URL.Query(), ¶ms.Status) - if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter status: %w", err), http.StatusBadRequest) - return - } - - // ------------- Optional query parameter "watch_status" ------------- - - err = runtime.BindQueryParameter("form", true, false, "watch_status", c.Request.URL.Query(), ¶ms.WatchStatus) - if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter watch_status: %w", err), http.StatusBadRequest) - return - } - - // ------------- Optional query parameter "rating" ------------- - - err = runtime.BindQueryParameter("form", true, false, "rating", c.Request.URL.Query(), ¶ms.Rating) - if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter rating: %w", err), http.StatusBadRequest) - return - } - - // ------------- Optional query parameter "release_year" ------------- - - err = runtime.BindQueryParameter("form", true, false, "release_year", c.Request.URL.Query(), ¶ms.ReleaseYear) - if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter release_year: %w", err), http.StatusBadRequest) - return - } - - // ------------- Optional query parameter "release_season" ------------- - - err = runtime.BindQueryParameter("form", true, false, "release_season", c.Request.URL.Query(), ¶ms.ReleaseSeason) - if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter release_season: %w", err), http.StatusBadRequest) - return - } - - // ------------- Optional query parameter "limit" ------------- - - err = runtime.BindQueryParameter("form", true, false, "limit", c.Request.URL.Query(), ¶ms.Limit) - if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter limit: %w", err), http.StatusBadRequest) - return - } - - // ------------- Optional query parameter "fields" ------------- - - err = runtime.BindQueryParameter("form", true, false, "fields", c.Request.URL.Query(), ¶ms.Fields) - if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter fields: %w", err), http.StatusBadRequest) - return - } - - for _, middleware := range siw.HandlerMiddlewares { - middleware(c) - if c.IsAborted() { - return - } - } - - siw.Handler.GetUsersUserIdTitles(c, userId, params) -} - -// GinServerOptions provides options for the Gin server. -type GinServerOptions struct { - BaseURL string - Middlewares []MiddlewareFunc - ErrorHandler func(*gin.Context, error, int) -} - -// RegisterHandlers creates http.Handler with routing matching OpenAPI spec. -func RegisterHandlers(router gin.IRouter, si ServerInterface) { - RegisterHandlersWithOptions(router, si, GinServerOptions{}) -} - -// RegisterHandlersWithOptions creates http.Handler with additional options -func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options GinServerOptions) { - errorHandler := options.ErrorHandler - if errorHandler == nil { - errorHandler = func(c *gin.Context, err error, statusCode int) { - c.JSON(statusCode, gin.H{"msg": err.Error()}) - } - } - - wrapper := ServerInterfaceWrapper{ - Handler: si, - HandlerMiddlewares: options.Middlewares, - ErrorHandler: errorHandler, - } - - router.GET(options.BaseURL+"/titles", wrapper.GetTitles) - router.GET(options.BaseURL+"/titles/:title_id", wrapper.GetTitlesTitleId) - router.GET(options.BaseURL+"/users/:user_id", wrapper.GetUsersUserId) - router.GET(options.BaseURL+"/users/:user_id/titles/", wrapper.GetUsersUserIdTitles) -} - -type GetTitlesRequestObject struct { - Params GetTitlesParams -} - -type GetTitlesResponseObject interface { - VisitGetTitlesResponse(w http.ResponseWriter) error -} - -type GetTitles200JSONResponse []Title - -func (response GetTitles200JSONResponse) VisitGetTitlesResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) - - return json.NewEncoder(w).Encode(response) -} - -type GetTitles204Response struct { -} - -func (response GetTitles204Response) VisitGetTitlesResponse(w http.ResponseWriter) error { - w.WriteHeader(204) - return nil -} - -type GetTitles400Response struct { -} - -func (response GetTitles400Response) VisitGetTitlesResponse(w http.ResponseWriter) error { - w.WriteHeader(400) - return nil -} - -type GetTitles500Response struct { -} - -func (response GetTitles500Response) VisitGetTitlesResponse(w http.ResponseWriter) error { - w.WriteHeader(500) - return nil -} - -type GetTitlesTitleIdRequestObject struct { - TitleId int64 `json:"title_id"` - Params GetTitlesTitleIdParams -} - -type GetTitlesTitleIdResponseObject interface { - VisitGetTitlesTitleIdResponse(w http.ResponseWriter) error -} - -type GetTitlesTitleId200JSONResponse Title - -func (response GetTitlesTitleId200JSONResponse) VisitGetTitlesTitleIdResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) - - return json.NewEncoder(w).Encode(response) -} - -type GetTitlesTitleId204Response struct { -} - -func (response GetTitlesTitleId204Response) VisitGetTitlesTitleIdResponse(w http.ResponseWriter) error { - w.WriteHeader(204) - return nil -} - -type GetTitlesTitleId400Response struct { -} - -func (response GetTitlesTitleId400Response) VisitGetTitlesTitleIdResponse(w http.ResponseWriter) error { - w.WriteHeader(400) - return nil -} - -type GetTitlesTitleId404Response struct { -} - -func (response GetTitlesTitleId404Response) VisitGetTitlesTitleIdResponse(w http.ResponseWriter) error { - w.WriteHeader(404) - return nil -} - -type GetTitlesTitleId500Response struct { -} - -func (response GetTitlesTitleId500Response) VisitGetTitlesTitleIdResponse(w http.ResponseWriter) error { - w.WriteHeader(500) - return nil -} - -type GetUsersUserIdRequestObject struct { - UserId string `json:"user_id"` - Params GetUsersUserIdParams -} - -type GetUsersUserIdResponseObject interface { - VisitGetUsersUserIdResponse(w http.ResponseWriter) error -} - -type GetUsersUserId200JSONResponse User - -func (response GetUsersUserId200JSONResponse) VisitGetUsersUserIdResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) - - return json.NewEncoder(w).Encode(response) -} - -type GetUsersUserId400Response struct { -} - -func (response GetUsersUserId400Response) VisitGetUsersUserIdResponse(w http.ResponseWriter) error { - w.WriteHeader(400) - return nil -} - -type GetUsersUserId404Response struct { -} - -func (response GetUsersUserId404Response) VisitGetUsersUserIdResponse(w http.ResponseWriter) error { - w.WriteHeader(404) - return nil -} - -type GetUsersUserId500Response struct { -} - -func (response GetUsersUserId500Response) VisitGetUsersUserIdResponse(w http.ResponseWriter) error { - w.WriteHeader(500) - return nil -} - -type GetUsersUserIdTitlesRequestObject struct { - UserId string `json:"user_id"` - Params GetUsersUserIdTitlesParams -} - -type GetUsersUserIdTitlesResponseObject interface { - VisitGetUsersUserIdTitlesResponse(w http.ResponseWriter) error -} - -type GetUsersUserIdTitles200JSONResponse []UserTitle - -func (response GetUsersUserIdTitles200JSONResponse) VisitGetUsersUserIdTitlesResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) - - return json.NewEncoder(w).Encode(response) -} - -type GetUsersUserIdTitles204Response struct { -} - -func (response GetUsersUserIdTitles204Response) VisitGetUsersUserIdTitlesResponse(w http.ResponseWriter) error { - w.WriteHeader(204) - return nil -} - -type GetUsersUserIdTitles400Response struct { -} - -func (response GetUsersUserIdTitles400Response) VisitGetUsersUserIdTitlesResponse(w http.ResponseWriter) error { - w.WriteHeader(400) - return nil -} - -type GetUsersUserIdTitles500Response struct { -} - -func (response GetUsersUserIdTitles500Response) VisitGetUsersUserIdTitlesResponse(w http.ResponseWriter) error { - w.WriteHeader(500) - return nil -} - -// StrictServerInterface represents all server handlers. -type StrictServerInterface interface { - // Get titles - // (GET /titles) - GetTitles(ctx context.Context, request GetTitlesRequestObject) (GetTitlesResponseObject, error) - // Get title description - // (GET /titles/{title_id}) - GetTitlesTitleId(ctx context.Context, request GetTitlesTitleIdRequestObject) (GetTitlesTitleIdResponseObject, error) - // Get user info - // (GET /users/{user_id}) - GetUsersUserId(ctx context.Context, request GetUsersUserIdRequestObject) (GetUsersUserIdResponseObject, error) - // Get user titles - // (GET /users/{user_id}/titles/) - GetUsersUserIdTitles(ctx context.Context, request GetUsersUserIdTitlesRequestObject) (GetUsersUserIdTitlesResponseObject, error) -} - -type StrictHandlerFunc = strictgin.StrictGinHandlerFunc -type StrictMiddlewareFunc = strictgin.StrictGinMiddlewareFunc - -func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { - return &strictHandler{ssi: ssi, middlewares: middlewares} -} - -type strictHandler struct { - ssi StrictServerInterface - middlewares []StrictMiddlewareFunc -} - -// GetTitles operation middleware -func (sh *strictHandler) GetTitles(ctx *gin.Context, params GetTitlesParams) { - var request GetTitlesRequestObject - - request.Params = params - - handler := func(ctx *gin.Context, request interface{}) (interface{}, error) { - return sh.ssi.GetTitles(ctx, request.(GetTitlesRequestObject)) - } - for _, middleware := range sh.middlewares { - handler = middleware(handler, "GetTitles") - } - - response, err := handler(ctx, request) - - if err != nil { - ctx.Error(err) - ctx.Status(http.StatusInternalServerError) - } else if validResponse, ok := response.(GetTitlesResponseObject); ok { - if err := validResponse.VisitGetTitlesResponse(ctx.Writer); err != nil { - ctx.Error(err) - } - } else if response != nil { - ctx.Error(fmt.Errorf("unexpected response type: %T", response)) - } -} - -// GetTitlesTitleId operation middleware -func (sh *strictHandler) GetTitlesTitleId(ctx *gin.Context, titleId int64, params GetTitlesTitleIdParams) { - var request GetTitlesTitleIdRequestObject - - request.TitleId = titleId - request.Params = params - - handler := func(ctx *gin.Context, request interface{}) (interface{}, error) { - return sh.ssi.GetTitlesTitleId(ctx, request.(GetTitlesTitleIdRequestObject)) - } - for _, middleware := range sh.middlewares { - handler = middleware(handler, "GetTitlesTitleId") - } - - response, err := handler(ctx, request) - - if err != nil { - ctx.Error(err) - ctx.Status(http.StatusInternalServerError) - } else if validResponse, ok := response.(GetTitlesTitleIdResponseObject); ok { - if err := validResponse.VisitGetTitlesTitleIdResponse(ctx.Writer); err != nil { - ctx.Error(err) - } - } else if response != nil { - ctx.Error(fmt.Errorf("unexpected response type: %T", response)) - } -} - -// GetUsersUserId operation middleware -func (sh *strictHandler) GetUsersUserId(ctx *gin.Context, userId string, params GetUsersUserIdParams) { - var request GetUsersUserIdRequestObject - - request.UserId = userId - request.Params = params - - handler := func(ctx *gin.Context, request interface{}) (interface{}, error) { - return sh.ssi.GetUsersUserId(ctx, request.(GetUsersUserIdRequestObject)) - } - for _, middleware := range sh.middlewares { - handler = middleware(handler, "GetUsersUserId") - } - - response, err := handler(ctx, request) - - if err != nil { - ctx.Error(err) - ctx.Status(http.StatusInternalServerError) - } else if validResponse, ok := response.(GetUsersUserIdResponseObject); ok { - if err := validResponse.VisitGetUsersUserIdResponse(ctx.Writer); err != nil { - ctx.Error(err) - } - } else if response != nil { - ctx.Error(fmt.Errorf("unexpected response type: %T", response)) - } -} - -// GetUsersUserIdTitles operation middleware -func (sh *strictHandler) GetUsersUserIdTitles(ctx *gin.Context, userId string, params GetUsersUserIdTitlesParams) { - var request GetUsersUserIdTitlesRequestObject - - request.UserId = userId - request.Params = params - - handler := func(ctx *gin.Context, request interface{}) (interface{}, error) { - return sh.ssi.GetUsersUserIdTitles(ctx, request.(GetUsersUserIdTitlesRequestObject)) - } - for _, middleware := range sh.middlewares { - handler = middleware(handler, "GetUsersUserIdTitles") - } - - response, err := handler(ctx, request) - - if err != nil { - ctx.Error(err) - ctx.Status(http.StatusInternalServerError) - } else if validResponse, ok := response.(GetUsersUserIdTitlesResponseObject); ok { - if err := validResponse.VisitGetUsersUserIdTitlesResponse(ctx.Writer); err != nil { - ctx.Error(err) - } - } else if response != nil { - ctx.Error(fmt.Errorf("unexpected response type: %T", response)) - } -} diff --git a/api/oapi-codegen.yaml b/api/oapi-codegen.yaml deleted file mode 100644 index 32e029a..0000000 --- a/api/oapi-codegen.yaml +++ /dev/null @@ -1,6 +0,0 @@ -package: oapi -generate: - strict-server: true - gin-server: true - models: true -output: api/api.gen.go \ No newline at end of file diff --git a/api/openapi.yaml b/api/openapi.yaml deleted file mode 100644 index a6ae12c..0000000 --- a/api/openapi.yaml +++ /dev/null @@ -1,837 +0,0 @@ -openapi: 3.1.1 -info: - title: Titles, Users, Reviews, Tags, and Media API - version: 1.0.0 - -servers: - - url: /api/v1 - -paths: - /titles: - get: - summary: Get titles - parameters: - - $ref: '#/components/parameters/cursor' - - $ref: '#/components/parameters/title_sort' - - in: query - name: sort_forward - default: true - schema: - type: boolean - - in: query - name: word - schema: - type: string - - in: query - name: status - schema: - $ref: '#/components/schemas/TitleStatus' - - in: query - name: rating - schema: - type: number - format: double - - in: query - name: release_year - schema: - type: integer - format: int32 - - in: query - name: release_season - schema: - $ref: '#/components/schemas/ReleaseSeason' - - in: query - name: limit - schema: - type: integer - format: int32 - default: 10 - - in: query - name: offset - schema: - type: integer - format: int32 - default: 0 - - in: query - name: fields - schema: - type: string - default: all - responses: - '200': - description: List of titles - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/Title' - '204': - description: No titles found - '400': - description: Request params are not correct - '500': - description: Unknown server error - - /titles/{title_id}: - get: - summary: Get title description - parameters: - - in: path - name: title_id - required: true - schema: - type: integer - format: int64 - - in: query - name: fields - schema: - type: string - default: all - responses: - '200': - description: Title description - content: - application/json: - schema: - $ref: '#/components/schemas/Title' - '404': - description: Title not found - '400': - description: Request params are not correct - '500': - description: Unknown server error - '204': - description: No title found - -# patch: -# summary: Update title info -# parameters: -# - in: path -# name: title_id -# required: true -# schema: -# type: string -# requestBody: -# required: true -# content: -# application/json: -# schema: -# $ref: '#/components/schemas/Title' -# responses: -# '200': -# description: Update result -# content: -# application/json: -# schema: -# type: object -# properties: -# success: -# type: boolean -# error: -# type: string -# user_json: -# $ref: '#/components/schemas/User' - -# /titles/{title_id}/reviews: -# get: -# summary: Get title reviews -# parameters: -# - in: path -# name: title_id -# required: true -# schema: -# type: string -# - in: query -# name: limit -# schema: -# type: integer -# default: 10 -# - in: query -# name: offset -# schema: -# type: integer -# default: 0 -# responses: -# '200': -# description: List of reviews -# content: -# application/json: -# schema: -# type: array -# items: -# $ref: '#/components/schemas/Review' -# '204': -# description: No reviews found - - /users/{user_id}: - get: - summary: Get user info - parameters: - - in: path - name: user_id - required: true - schema: - type: string - - in: query - name: fields - schema: - type: string - default: all - responses: - '200': - description: User info - content: - application/json: - schema: - $ref: '#/components/schemas/User' - '404': - description: User not found - '400': - description: Request params are not correct - '500': - description: Unknown server error - - # patch: - # summary: Update user - # parameters: - # - in: path - # name: user_id - # required: true - # schema: - # type: string - # requestBody: - # required: true - # content: - # application/json: - # schema: - # $ref: '#/components/schemas/User' - # responses: - # '200': - # description: Update result - # content: - # application/json: - # schema: - # type: object - # properties: - # success: - # type: boolean - # error: - # type: string - - # delete: - # summary: Delete user - # parameters: - # - in: path - # name: user_id - # required: true - # schema: - # type: string - # responses: - # '200': - # description: Delete result - # content: - # application/json: - # schema: - # type: object - # properties: - # success: - # type: boolean - # error: - # type: string - - # /users: - # get: - # summary: Search user - # parameters: - # - in: query - # name: query - # schema: - # type: string - # - in: query - # name: fields - # schema: - # type: string - # responses: - # '200': - # description: List of users - # content: - # application/json: - # schema: - # type: array - # items: - # $ref: '#/components/schemas/User' - - # post: - # summary: Add new user - # requestBody: - # required: true - # content: - # application/json: - # schema: - # $ref: '#/components/schemas/User' - # responses: - # '200': - # description: Add result - # content: - # application/json: - # schema: - # type: object - # properties: - # success: - # type: boolean - # error: - # type: string - # user_json: - # $ref: '#/components/schemas/User' - - /users/{user_id}/titles/: - get: - summary: Get user titles - parameters: - - $ref: '#/components/parameters/cursor' - - in: path - name: user_id - required: true - schema: - type: string - - in: query - name: word - schema: - type: string - - in: query - name: status - schema: - $ref: '#/components/schemas/TitleStatus' - - in: query - name: watch_status - schema: - $ref: '#/components/schemas/UserTitleStatus' - - in: query - name: rating - schema: - type: number - format: double - - in: query - name: release_year - schema: - type: integer - format: int32 - - in: query - name: release_season - schema: - $ref: '#/components/schemas/ReleaseSeason' - - in: query - name: limit - schema: - type: integer - format: int32 - default: 10 - - in: query - name: fields - schema: - type: string - default: all - responses: - '200': - description: List of user titles - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/UserTitle' - '204': - description: No titles found - '400': - description: Request params are not correct - '500': - description: Unknown server error - -# post: -# summary: Add user title -# parameters: -# - in: path -# name: user_id -# required: true -# schema: -# type: string -# requestBody: -# required: true -# content: -# application/json: -# schema: -# type: object -# properties: -# title_id: -# type: string -# status: -# type: string -# responses: -# '200': -# description: Add result -# content: -# application/json: -# schema: -# type: object -# properties: -# success: -# type: boolean -# error: -# type: string - -# patch: -# summary: Update user title -# parameters: -# - in: path -# name: user_id -# required: true -# schema: -# type: string -# requestBody: -# required: true -# content: -# application/json: -# schema: -# $ref: '#/components/schemas/UserTitle' -# responses: -# '200': -# description: Update result -# content: -# application/json: -# schema: -# type: object -# properties: -# success: -# type: boolean -# error: -# type: string - -# delete: -# summary: Delete user title -# parameters: -# - in: path -# name: user_id -# required: true -# schema: -# type: string -# - in: query -# name: title_id -# schema: -# type: string -# responses: -# '200': -# description: Delete result -# content: -# application/json: -# schema: -# type: object -# properties: -# success: -# type: boolean -# error: -# type: string - -# /users/{user_id}/reviews: -# get: -# summary: Get user reviews -# parameters: -# - in: path -# name: user_id -# required: true -# schema: -# type: string -# - in: query -# name: limit -# schema: -# type: integer -# default: 10 -# - in: query -# name: offset -# schema: -# type: integer -# default: 0 -# responses: -# '200': -# description: List of reviews -# content: -# application/json: -# schema: -# type: array -# items: -# $ref: '#/components/schemas/Review' - -# /reviews: -# post: -# summary: Add review -# requestBody: -# required: true -# content: -# application/json: -# schema: -# $ref: '#/components/schemas/Review' -# responses: -# '200': -# description: Add result -# content: -# application/json: -# schema: -# type: object -# properties: -# success: -# type: boolean -# error: -# type: string - -# /reviews/{review_id}: -# patch: -# summary: Update review -# parameters: -# - in: path -# name: review_id -# required: true -# schema: -# type: string -# requestBody: -# required: true -# content: -# application/json: -# schema: -# $ref: '#/components/schemas/Review' -# responses: -# '200': -# description: Update result -# content: -# application/json: -# schema: -# type: object -# properties: -# success: -# type: boolean -# error: -# type: string -# delete: -# summary: Delete review -# parameters: -# - in: path -# name: review_id -# required: true -# schema: -# type: string -# responses: -# '200': -# description: Delete result -# content: -# application/json: -# schema: -# type: object -# properties: -# success: -# type: boolean -# error: -# type: string - -# /tags: -# get: -# summary: Get tags -# parameters: -# - in: query -# name: limit -# schema: -# type: integer -# default: 10 -# - in: query -# name: offset -# schema: -# type: integer -# default: 0 -# - in: query -# name: fields -# schema: -# type: string -# responses: -# '200': -# description: List of tags -# content: -# application/json: -# schema: -# type: array -# items: -# $ref: '#/components/schemas/Tag' - -# /media: -# post: -# summary: Upload image -# responses: -# '200': -# description: Upload result -# content: -# application/json: -# schema: -# type: object -# properties: -# success: -# type: boolean -# error: -# type: string -# image_id: -# type: string - -# get: -# summary: Get image path -# parameters: -# - in: query -# name: image_id -# required: true -# schema: -# type: string -# responses: -# '200': -# description: Image path -# content: -# application/json: -# schema: -# type: object -# properties: -# success: -# type: boolean -# error: -# type: string -# image_path: -# type: string - -components: - parameters: - cursor: # example base64( {id: 1, param: 2019}) - in: query - name: cursor - required: false - schema: - type: string - - title_sort: - in: query - name: sort - required: false - schema: - $ref: '#/components/schemas/TitleSort' - - schemas: - TitleSort: - type: string - description: Title sort order - default: id - enum: - - id - - year - - rating - - views - - Image: - type: object - properties: - id: - type: integer - format: int64 - storage_type: - type: string - image_path: - type: string - - TitleStatus: - type: string - description: Title status - enum: - - finished - - ongoing - - planned - - ReleaseSeason: - type: string - description: Title release season - enum: - - winter - - spring - - summer - - fall - - UserTitleStatus: - type: string - description: User's title status - enum: - - finished - - planned - - dropped - - in-progress - - Review: - type: object - additionalProperties: true - - Tag: - type: object - description: "A localized tag: keys are language codes (ISO 639-1), values are tag names" - additionalProperties: - type: string - example: - en: "Shojo" - ru: "Сёдзё" - ja: "少女" - - Tags: - type: array - description: "Array of localized tags" - items: - $ref: '#/components/schemas/Tag' - example: - - en: "Shojo" - ru: "Сёдзё" - ja: "少女" - - en: "Shounen" - ru: "Сёнен" - ja: "少年" - - Studio: - type: object - required: - - id - - name - properties: - id: - type: integer - format: int64 - name: - type: string - poster: - $ref: '#/components/schemas/Image' - description: - type: string - - - Title: - type: object - required: - - id - - title_names - - tags - properties: - id: - type: integer - format: int64 - description: Unique title ID (primary key) - example: 1 - title_names: - type: object - description: "Localized titles. Key = language (ISO 639-1), value = list of names" - additionalProperties: - type: array - items: - type: string - example: "Attack on Titan" - minItems: 1 - example: ["Attack on Titan", "AoT"] - example: - en: ["Attack on Titan", "AoT"] - ru: ["Атака титанов", "Титаны"] - ja: ["進撃の巨人"] - studio: - $ref: '#/components/schemas/Studio' - tags: - $ref: '#/components/schemas/Tags' - poster: - $ref: '#/components/schemas/Image' - title_status: - $ref: '#/components/schemas/TitleStatus' - rating: - type: number - format: double - rating_count: - type: integer - format: int32 - release_year: - type: integer - format: int32 - release_season: - $ref: '#/components/schemas/ReleaseSeason' - episodes_aired: - type: integer - format: int32 - episodes_all: - type: integer - format: int32 - episodes_len: - type: object - additionalProperties: - type: number - format: double - additionalProperties: true - - User: - type: object - properties: - id: - type: integer - format: int64 - description: Unique user ID (primary key) - example: 1 - avatar_id: - type: integer - format: int64 - description: ID of the user avatar (references images table) - nullable: true - example: null - mail: - type: string - format: email - description: User email - example: "john.doe@example.com" - nickname: - type: string - description: Username (alphanumeric + _ or -) - maxLength: 16 - example: "john_doe_42" - disp_name: - type: string - description: Display name - maxLength: 32 - example: "John Doe" - user_desc: - type: string - description: User description - maxLength: 512 - example: "Just a regular user." - creation_date: - type: string - format: date-time - description: Timestamp when the user was created - example: "2025-10-10T23:45:47.908073Z" - required: - - user_id - - nickname - # - creation_date - - UserTitle: - type: object - required: - - user_id - - title_id - - status - properties: - user_id: - type: integer - format: int64 - title_id: - type: integer - format: int64 - status: - $ref: '#/components/schemas/UserTitleStatus' - rate: - type: integer - format: int32 - review_id: - type: integer - format: int64 - ctime: - type: string - format: date-time - additionalProperties: true diff --git a/deploy/buildx.sh b/deploy/buildx.sh index 8c19bc9..96e0fe4 100644 --- a/deploy/buildx.sh +++ b/deploy/buildx.sh @@ -1,7 +1,2 @@ -#!/usr/bin/env bash - -export BACKEND_API_BASE_URL="http://127.0.0.1:8080" - -docker buildx build --platform linux/amd64 -t meowgit.nekoea.red/nihonium/forgejo-runner:latest -f ./Dockerfiles/Dockerfile_forgejo-runner . --push -docker buildx build --build-arg VITE_BACKEND_API_BASE_URL=${BACKEND_API_BASE_URL} --platform linux/amd64 -t meowgit.nekoea.red/nihonium/nyanimedb-frontend:latest -f .\Dockerfiles\Dockerfile_frontend . --push -docker buildx build --platform linux/amd64 -t meowgit.nekoea.red/nihonium/nyanimedb-backend:latest -f .\Dockerfiles\Dockerfile_backend . --push \ No newline at end of file +#!/bin/bash +docker buildx build --platform linux/amd64 -t meowgit.nekoea.red/nihonium/forgejo-runner:latest -f ./Dockerfiles/Dockerfile_forgejo-runner . --push \ No newline at end of file diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 1a96253..266f9d5 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -10,7 +10,7 @@ services: ports: - "${POSTGRES_PORT}:5432" volumes: - - postgres_data:/var/lib/postgresql + - postgres_data:/var/lib/postgresql/data # pgadmin: # image: dpage/pgadmin4:${PGADMIN_VERSION} @@ -32,7 +32,6 @@ services: restart: always environment: LOG_LEVEL: ${LOG_LEVEL} - DATABASE_URL: ${DATABASE_URL} ports: - "8080:8080" depends_on: diff --git a/deploy/generate.sh b/deploy/generate.sh deleted file mode 100644 index d7d94a2..0000000 --- a/deploy/generate.sh +++ /dev/null @@ -1,3 +0,0 @@ -npx openapi-typescript-codegen --input ..\..\api\openapi.yaml --output ./src/api --client axios -oapi-codegen --config=api/oapi-codegen.yaml .\api\openapi.yaml -sqlc generate -f .\sql\sqlc.yaml \ No newline at end of file diff --git a/modules/backend/handlers/common.go b/modules/backend/handlers/common.go deleted file mode 100644 index b85d022..0000000 --- a/modules/backend/handlers/common.go +++ /dev/null @@ -1,19 +0,0 @@ -package handlers - -import ( - sqlc "nyanimedb/sql" - "strconv" -) - -type Server struct { - db *sqlc.Queries -} - -func NewServer(db *sqlc.Queries) Server { - return Server{db: db} -} - -func parseInt64(s string) (int32, error) { - i, err := strconv.ParseInt(s, 10, 64) - return int32(i), err -} diff --git a/modules/backend/handlers/titles.go b/modules/backend/handlers/titles.go deleted file mode 100644 index 46ff982..0000000 --- a/modules/backend/handlers/titles.go +++ /dev/null @@ -1,262 +0,0 @@ -package handlers - -import ( - "context" - "encoding/json" - "fmt" - oapi "nyanimedb/api" - sqlc "nyanimedb/sql" - - "github.com/jackc/pgx/v5" - log "github.com/sirupsen/logrus" -) - -func Word2Sqlc(s *string) *string { - if s == nil || *s == "" { - return nil - } - - return s -} - -func TitleStatus2Sqlc(s *oapi.TitleStatus) (*sqlc.TitleStatusT, error) { - if s == nil { - return nil, nil - } - var t sqlc.TitleStatusT - switch *s { - case oapi.TitleStatusFinished: - t = sqlc.TitleStatusTFinished - case oapi.TitleStatusOngoing: - t = sqlc.TitleStatusTOngoing - case oapi.TitleStatusPlanned: - t = sqlc.TitleStatusTPlanned - default: - return nil, fmt.Errorf("unexpected tittle status: %s", *s) - } - return &t, nil -} - -func ReleaseSeason2sqlc(s *oapi.ReleaseSeason) (*sqlc.ReleaseSeasonT, error) { - if s == nil { - return nil, nil - } - var t sqlc.ReleaseSeasonT - switch *s { - case oapi.Winter: - t = sqlc.ReleaseSeasonTWinter - case oapi.Spring: - t = sqlc.ReleaseSeasonTSpring - case oapi.Summer: - t = sqlc.ReleaseSeasonTSummer - case oapi.Fall: - t = sqlc.ReleaseSeasonTFall - default: - return nil, fmt.Errorf("unexpected release season: %s", *s) - } - return &t, nil -} - -func (s Server) GetTagsByTitleId(ctx context.Context, id int64) (oapi.Tags, error) { - - sqlc_title_tags, err := s.db.GetTitleTags(ctx, id) - if err != nil { - if err == pgx.ErrNoRows { - return nil, nil - } - return nil, fmt.Errorf("query GetTitleTags: %v", err) - } - - oapi_tag_names := make(oapi.Tags, 1) - for _, title_tag := range sqlc_title_tags { - oapi_tag_name := make(map[string]string, 1) - err = json.Unmarshal(title_tag, &oapi_tag_name) - if err != nil { - return nil, fmt.Errorf("unmarshalling title_tag: %v", err) - } - oapi_tag_names = append(oapi_tag_names, oapi_tag_name) - } - - return oapi_tag_names, nil -} - -func (s Server) GetImage(ctx context.Context, id int64) (*oapi.Image, error) { - - var oapi_image oapi.Image - - sqlc_image, err := s.db.GetImageByID(ctx, id) - if err != nil { - if err == pgx.ErrNoRows { - return nil, nil //todo: error reference in db - } - return &oapi_image, fmt.Errorf("query GetImageByID: %v", err) - } - - //can cast and dont use brain cause all this fields required in image table - oapi_image.Id = &sqlc_image.ID - oapi_image.ImagePath = &sqlc_image.ImagePath - storageTypeStr := string(sqlc_image.StorageType) - oapi_image.StorageType = &storageTypeStr - - return &oapi_image, nil -} - -func (s Server) GetStudio(ctx context.Context, id int64) (*oapi.Studio, error) { - - var oapi_studio oapi.Studio - - sqlc_studio, err := s.db.GetStudioByID(ctx, id) - if err != nil { - if err == pgx.ErrNoRows { - return nil, nil - } - return &oapi_studio, fmt.Errorf("query GetStudioByID: %v", err) - } - - oapi_studio.Id = sqlc_studio.ID - oapi_studio.Name = sqlc_studio.StudioName - oapi_studio.Description = sqlc_studio.StudioDesc - - if sqlc_studio.IllustID == nil { - return &oapi_studio, nil - } - oapi_illust, err := s.GetImage(ctx, *sqlc_studio.IllustID) - if err != nil { - return &oapi_studio, fmt.Errorf("GetImage: %v", err) - } - if oapi_illust != nil { - oapi_studio.Poster = oapi_illust - } - - return &oapi_studio, nil -} - -func (s Server) mapTitle(ctx context.Context, title sqlc.Title) (oapi.Title, error) { - - var oapi_title oapi.Title - - title_names := make(map[string][]string, 1) - err := json.Unmarshal(title.TitleNames, &title_names) - if err != nil { - return oapi_title, fmt.Errorf("unmarshal TitleNames: %v", err) - } - - episodes_lens := make(map[string]float64, 1) - err = json.Unmarshal(title.EpisodesLen, &episodes_lens) - if err != nil { - return oapi_title, fmt.Errorf("unmarshal EpisodesLen: %v", err) - } - - oapi_tag_names, err := s.GetTagsByTitleId(ctx, title.ID) - if err != nil { - return oapi_title, fmt.Errorf("GetTagsByTitleId: %v", err) - } - if oapi_tag_names != nil { - oapi_title.Tags = oapi_tag_names - } - - if title.PosterID != nil { - oapi_image, err := s.GetImage(ctx, *title.PosterID) - if err != nil { - return oapi_title, fmt.Errorf("GetImage: %v", err) - } - if oapi_image != nil { - oapi_title.Poster = oapi_image - } - } - - oapi_studio, err := s.GetStudio(ctx, title.StudioID) - if err != nil { - return oapi_title, fmt.Errorf("GetStudio: %v", err) - } - if oapi_studio != nil { - oapi_title.Studio = oapi_studio - } - - if title.ReleaseSeason != nil { - rs := oapi.ReleaseSeason(*title.ReleaseSeason) - oapi_title.ReleaseSeason = &rs - } else { - oapi_title.ReleaseSeason = nil - } - - ts := oapi.TitleStatus(title.TitleStatus) - oapi_title.TitleStatus = &ts - - oapi_title.Id = title.ID - oapi_title.Rating = title.Rating - oapi_title.RatingCount = title.RatingCount - oapi_title.ReleaseYear = title.ReleaseYear - oapi_title.TitleNames = title_names - oapi_title.EpisodesAired = title.EpisodesAired - oapi_title.EpisodesAll = title.EpisodesAll - oapi_title.EpisodesLen = &episodes_lens - - return oapi_title, nil -} - -func (s Server) GetTitlesTitleId(ctx context.Context, request oapi.GetTitlesTitleIdRequestObject) (oapi.GetTitlesTitleIdResponseObject, error) { - var oapi_title oapi.Title - - sqlc_title, err := s.db.GetTitleByID(ctx, request.TitleId) - if err != nil { - if err == pgx.ErrNoRows { - return oapi.GetTitlesTitleId204Response{}, nil - } - log.Errorf("%v", err) - return oapi.GetTitlesTitleId500Response{}, nil - } - - oapi_title, err = s.mapTitle(ctx, sqlc_title) - if err != nil { - log.Errorf("%v", err) - return oapi.GetTitlesTitleId500Response{}, nil - } - - return oapi.GetTitlesTitleId200JSONResponse(oapi_title), nil -} - -func (s Server) GetTitles(ctx context.Context, request oapi.GetTitlesRequestObject) (oapi.GetTitlesResponseObject, error) { - opai_titles := make([]oapi.Title, 0) - - word := Word2Sqlc(request.Params.Word) - status, err := TitleStatus2Sqlc(request.Params.Status) - if err != nil { - log.Errorf("%v", err) - return oapi.GetTitles400Response{}, err - } - season, err := ReleaseSeason2sqlc(request.Params.ReleaseSeason) - if err != nil { - log.Errorf("%v", err) - return oapi.GetTitles400Response{}, err - } - // param = nil means it will not be used - titles, err := s.db.SearchTitles(ctx, sqlc.SearchTitlesParams{ - Word: word, - Status: status, - Rating: request.Params.Rating, - ReleaseYear: request.Params.ReleaseYear, - ReleaseSeason: season, - Offset: request.Params.Offset, - Limit: request.Params.Limit, - }) - if err != nil { - log.Errorf("%v", err) - return oapi.GetTitles500Response{}, nil - } - if len(titles) == 0 { - return oapi.GetTitles204Response{}, nil - } - - for _, title := range titles { - - t, err := s.mapTitle(ctx, title) - if err != nil { - log.Errorf("%v", err) - return oapi.GetTitles500Response{}, nil - } - opai_titles = append(opai_titles, t) - } - - return oapi.GetTitles200JSONResponse(opai_titles), nil -} diff --git a/modules/backend/handlers/users.go b/modules/backend/handlers/users.go deleted file mode 100644 index 0fa903f..0000000 --- a/modules/backend/handlers/users.go +++ /dev/null @@ -1,83 +0,0 @@ -package handlers - -import ( - "context" - oapi "nyanimedb/api" - sqlc "nyanimedb/sql" - "time" - - "github.com/jackc/pgx/v5" - "github.com/oapi-codegen/runtime/types" -) - -// type Server struct { -// db *sqlc.Queries -// } - -// func NewServer(db *sqlc.Queries) Server { -// return Server{db: db} -// } - -// func parseInt64(s string) (int32, error) { -// i, err := strconv.ParseInt(s, 10, 64) -// return int32(i), err -// } - -func mapUser(u sqlc.GetUserByIDRow) oapi.User { - return oapi.User{ - AvatarId: u.AvatarID, - CreationDate: &u.CreationDate, - DispName: u.DispName, - Id: &u.ID, - Mail: (*types.Email)(u.Mail), - Nickname: u.Nickname, - UserDesc: u.UserDesc, - } -} - -func (s Server) GetUsersUserId(ctx context.Context, req oapi.GetUsersUserIdRequestObject) (oapi.GetUsersUserIdResponseObject, error) { - userID, err := parseInt64(req.UserId) - if err != nil { - return oapi.GetUsersUserId404Response{}, nil - } - user, err := s.db.GetUserByID(context.TODO(), int64(userID)) - if err != nil { - if err == pgx.ErrNoRows { - return oapi.GetUsersUserId404Response{}, nil - } - return nil, err - } - return oapi.GetUsersUserId200JSONResponse(mapUser(user)), nil -} - -func (s Server) GetUsersUserIdTitles(ctx context.Context, request oapi.GetUsersUserIdTitlesRequestObject) (oapi.GetUsersUserIdTitlesResponseObject, error) { - - var rate int32 = 9 - var review_id int64 = 3 - time := time.Date(2025, 1, 15, 10, 30, 0, 0, time.UTC) - - var userTitles = []oapi.UserTitle{ - { - UserId: 101, - TitleId: 2001, - Status: oapi.UserTitleStatusFinished, - Rate: &rate, - Ctime: &time, - }, - { - UserId: 102, - TitleId: 2002, - Status: oapi.UserTitleStatusInProgress, - ReviewId: &review_id, - Ctime: &time, - }, - { - UserId: 103, - TitleId: 2003, - Status: oapi.UserTitleStatusDropped, - Ctime: &time, - }, - } - - return oapi.GetUsersUserIdTitles200JSONResponse(userTitles), nil -} diff --git a/modules/backend/main.go b/modules/backend/main.go deleted file mode 100644 index 42a66d3..0000000 --- a/modules/backend/main.go +++ /dev/null @@ -1,119 +0,0 @@ -package main - -import ( - "context" - "fmt" - sqlc "nyanimedb/sql" - "os" - "reflect" - "time" - - oapi "nyanimedb/api" - handlers "nyanimedb/modules/backend/handlers" - - "github.com/gin-contrib/cors" - "github.com/gin-gonic/gin" - "github.com/jackc/pgx/v5" - "github.com/pelletier/go-toml/v2" -) - -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) - // } - - conn, err := pgx.Connect(context.Background(), os.Getenv("DATABASE_URL")) - if err != nil { - fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err) - os.Exit(1) - } - - defer conn.Close(context.Background()) - - r := gin.Default() - - queries := sqlc.New(conn) - - server := handlers.NewServer(queries) - // r.LoadHTMLGlob("templates/*") - - r.Use(cors.New(cors.Config{ - 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"}, - AllowCredentials: true, - MaxAge: 12 * time.Hour, - })) - - oapi.RegisterHandlers(r, oapi.NewStrictHandler( - server, - // сюда можно добавить middlewares, если нужно - []oapi.StrictMiddlewareFunc{}, - )) - // r.GET("/", func(c *gin.Context) { - // c.HTML(http.StatusOK, "index.html", gin.H{ - // "title": "Welcome Page", - // "message": "Hello, Gin with HTML templates!", - // }) - // }) - - // r.GET("/api", func(c *gin.Context) { - // items := []Item{ - // {ID: 1, Title: "First Item", Description: "This is the description of the first item."}, - // {ID: 2, Title: "Second Item", Description: "This is the description of the second item."}, - // {ID: 3, Title: "Third Item", Description: "This is the description of the third item."}, - // } - - // c.JSON(http.StatusOK, items) - // }) - - r.Run(":8080") -} - -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/backend/queries.sql b/modules/backend/queries.sql deleted file mode 100644 index 423be37..0000000 --- a/modules/backend/queries.sql +++ /dev/null @@ -1,208 +0,0 @@ --- name: GetImageByID :one -SELECT id, storage_type, image_path -FROM images -WHERE id = sqlc.arg('illust_id')::bigint; - --- name: CreateImage :one -INSERT INTO images (storage_type, image_path) -VALUES ($1, $2) -RETURNING id, storage_type, image_path; - --- name: GetUserByID :one -SELECT id, avatar_id, mail, nickname, disp_name, user_desc, creation_date -FROM users -WHERE id = $1; - - --- name: GetStudioByID :one -SELECT * -FROM studios -WHERE id = sqlc.arg('studio_id')::bigint; - --- name: InsertStudio :one -INSERT INTO studios (studio_name, illust_id, studio_desc) -VALUES ( - sqlc.arg('studio_name')::text, - sqlc.narg('illust_id')::bigint, - sqlc.narg('studio_desc')::text) -RETURNING id, studio_name, illust_id, studio_desc; - --- name: GetTitleTags :many -SELECT - tag_names -FROM tags as g -JOIN title_tags as t ON(t.tag_id = g.id) -WHERE t.title_id = sqlc.arg('title_id')::bigint; - --- name: InsertTitleTags :one -INSERT INTO title_tags (title_id, tag_id) -VALUES ( - sqlc.arg('title_id')::bigint, - sqlc.arg('tag_id')::bigint) -RETURNING title_id, tag_id; - --- name: InsertTag :one -INSERT INTO tags (tag_names) -VALUES ( - sqlc.arg('tag_names')::jsonb) -RETURNING id, tag_names; - --- -- name: ListUsers :many --- SELECT user_id, avatar_id, passhash, mail, nickname, disp_name, user_desc, creation_date --- FROM users --- ORDER BY user_id --- LIMIT $1 OFFSET $2; - --- -- name: CreateUser :one --- INSERT INTO users (avatar_id, passhash, mail, nickname, disp_name, user_desc, creation_date) --- VALUES ($1, $2, $3, $4, $5, $6, $7) --- RETURNING user_id, avatar_id, nickname, disp_name, user_desc, creation_date; - --- -- name: UpdateUser :one --- UPDATE users --- SET --- avatar_id = COALESCE(sqlc.narg('avatar_id'), avatar_id), --- disp_name = COALESCE(sqlc.narg('disp_name'), disp_name), --- user_desc = COALESCE(sqlc.narg('user_desc'), user_desc), --- passhash = COALESCE(sqlc.narg('passhash'), passhash) --- WHERE user_id = sqlc.arg('user_id') --- RETURNING user_id, avatar_id, nickname, disp_name, user_desc, creation_date; - --- -- name: DeleteUser :exec --- DELETE FROM users --- WHERE user_id = $1; - --- name: GetTitleByID :one -SELECT * -FROM titles -WHERE id = sqlc.arg('title_id')::bigint; - --- name: SearchTitles :many -SELECT - * -FROM titles -WHERE - CASE - WHEN sqlc.narg('word')::text IS NOT NULL THEN - ( - SELECT bool_and( - EXISTS ( - SELECT 1 - FROM jsonb_each_text(title_names) AS t(key, val) - WHERE val ILIKE pattern - ) - ) - FROM unnest( - ARRAY( - SELECT '%' || trim(w) || '%' - FROM unnest(string_to_array(sqlc.narg('word')::text, ' ')) AS w - WHERE trim(w) <> '' - ) - ) AS pattern - ) - ELSE true - END - - AND (sqlc.narg('status')::title_status_t IS NULL OR title_status = sqlc.narg('status')::title_status_t) - AND (sqlc.narg('rating')::float IS NULL OR rating >= sqlc.narg('rating')::float) - AND (sqlc.narg('release_year')::int IS NULL OR release_year = sqlc.narg('release_year')::int) - AND (sqlc.narg('release_season')::release_season_t IS NULL OR release_season = sqlc.narg('release_season')::release_season_t) - -LIMIT COALESCE(sqlc.narg('limit')::int, 100) -- 100 is default limit -OFFSET sqlc.narg('offset')::int; - --- -- name: ListTitles :many --- SELECT title_id, title_names, studio_id, poster_id, signal_ids, --- title_status, rating, rating_count, release_year, release_season, --- season, episodes_aired, episodes_all, episodes_len --- FROM titles --- ORDER BY title_id --- LIMIT $1 OFFSET $2; - --- -- name: UpdateTitle :one --- UPDATE titles --- SET --- title_names = COALESCE(sqlc.narg('title_names'), title_names), --- studio_id = COALESCE(sqlc.narg('studio_id'), studio_id), --- poster_id = COALESCE(sqlc.narg('poster_id'), poster_id), --- signal_ids = COALESCE(sqlc.narg('signal_ids'), signal_ids), --- title_status = COALESCE(sqlc.narg('title_status'), title_status), --- release_year = COALESCE(sqlc.narg('release_year'), release_year), --- release_season = COALESCE(sqlc.narg('release_season'), release_season), --- episodes_aired = COALESCE(sqlc.narg('episodes_aired'), episodes_aired), --- episodes_all = COALESCE(sqlc.narg('episodes_all'), episodes_all), --- episodes_len = COALESCE(sqlc.narg('episodes_len'), episodes_len) --- WHERE title_id = sqlc.arg('title_id') --- RETURNING *; - --- name: GetReviewByID :one -SELECT * -FROM reviews -WHERE review_id = sqlc.arg('review_id')::bigint; - --- -- name: CreateReview :one --- INSERT INTO reviews (user_id, title_id, image_ids, review_text, creation_date) --- VALUES ($1, $2, $3, $4, $5) --- RETURNING review_id, user_id, title_id, image_ids, review_text, creation_date; - --- -- name: UpdateReview :one --- UPDATE reviews --- SET --- image_ids = COALESCE(sqlc.narg('image_ids'), image_ids), --- review_text = COALESCE(sqlc.narg('review_text'), review_text) --- WHERE review_id = sqlc.arg('review_id') --- RETURNING *; - --- -- name: DeleteReview :exec --- DELETE FROM reviews --- WHERE review_id = $1; - --- name: ListReviewsByTitle :many --- SELECT review_id, user_id, title_id, image_ids, review_text, creation_date --- FROM reviews --- WHERE title_id = $1 --- ORDER BY creation_date DESC --- LIMIT $2 OFFSET $3; - --- -- name: ListReviewsByUser :many --- SELECT review_id, user_id, title_id, image_ids, review_text, creation_date --- FROM reviews --- WHERE user_id = $1 --- ORDER BY creation_date DESC --- LIMIT $2 OFFSET $3; - --- -- name: GetUserTitle :one --- SELECT usertitle_id, user_id, title_id, status, rate, review_id --- FROM usertitles --- WHERE user_id = $1 AND title_id = $2; - --- -- name: ListUserTitles :many --- SELECT usertitle_id, user_id, title_id, status, rate, review_id --- FROM usertitles --- WHERE user_id = $1 --- ORDER BY usertitle_id --- LIMIT $2 OFFSET $3; - --- -- name: CreateUserTitle :one --- INSERT INTO usertitles (user_id, title_id, status, rate, review_id) --- VALUES ($1, $2, $3, $4, $5) --- RETURNING usertitle_id, user_id, title_id, status, rate, review_id; - --- -- name: UpdateUserTitle :one --- UPDATE usertitles --- SET --- status = COALESCE(sqlc.narg('status'), status), --- rate = COALESCE(sqlc.narg('rate'), rate), --- review_id = COALESCE(sqlc.narg('review_id'), review_id) --- WHERE user_id = $1 AND title_id = $2 --- RETURNING *; - --- -- name: DeleteUserTitle :exec --- DELETE FROM usertitles --- WHERE user_id = $1 AND ($2::int IS NULL OR title_id = $2); - --- -- name: ListTags :many --- SELECT tag_id, tag_names --- FROM tags --- ORDER BY tag_id --- LIMIT $1 OFFSET $2; \ No newline at end of file diff --git a/modules/backend/types.go b/modules/backend/types.go deleted file mode 100644 index 20d3158..0000000 --- a/modules/backend/types.go +++ /dev/null @@ -1,12 +0,0 @@ -package main - -type Config struct { - 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/nginx-default.conf b/modules/frontend/nginx-default.conf deleted file mode 100644 index a538968..0000000 --- a/modules/frontend/nginx-default.conf +++ /dev/null @@ -1,29 +0,0 @@ -server { - listen 80; - listen [::]:80; - server_name localhost; - - root /usr/share/nginx/html; - index index.html; - - location / { - try_files $uri $uri/ /index.html; - } - - location /api/v1/ { - rewrite ^/api/v1/(.*)$ /$1 break; - proxy_pass http://nyanimedb-backend:8080/; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - proxy_set_header Host $host; - proxy_cache_bypass $http_upgrade; - } - #error_page 404 /404.html; - - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root /usr/share/nginx/html; - } - -} \ No newline at end of file diff --git a/modules/frontend/package-lock.json b/modules/frontend/package-lock.json index f5dde46..a282de5 100644 --- a/modules/frontend/package-lock.json +++ b/modules/frontend/package-lock.json @@ -8,13 +8,9 @@ "name": "nyanimedb-frontend", "version": "0.0.0", "dependencies": { - "@heroicons/react": "^2.2.0", - "@tailwindcss/vite": "^4.1.17", "axios": "^1.12.2", "react": "^19.1.1", - "react-dom": "^19.1.1", - "react-router-dom": "^7.9.4", - "tailwindcss": "^4.1.17" + "react-dom": "^19.1.1" }, "devDependencies": { "@eslint/js": "^9.36.0", @@ -26,30 +22,11 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.22", "globals": "^16.4.0", - "openapi-typescript-codegen": "^0.29.0", "typescript": "~5.9.3", "typescript-eslint": "^8.45.0", "vite": "^7.1.7" } }, - "node_modules/@apidevtools/json-schema-ref-parser": { - "version": "11.9.3", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.3.tgz", - "integrity": "sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jsdevtools/ono": "^7.1.3", - "@types/json-schema": "^7.0.15", - "js-yaml": "^4.1.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/philsturgeon" - } - }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -340,6 +317,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -356,6 +334,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -372,6 +351,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -388,6 +368,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -404,6 +385,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -420,6 +402,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -436,6 +419,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -452,6 +436,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -468,6 +453,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -484,6 +470,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -500,6 +487,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -516,6 +504,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -532,6 +521,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -548,6 +538,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -564,6 +555,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -580,6 +572,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -596,6 +589,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -612,6 +606,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -628,6 +623,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -644,6 +640,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -660,6 +657,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -676,6 +674,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -692,6 +691,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -708,6 +708,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -724,6 +725,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -740,6 +742,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -906,15 +909,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@heroicons/react": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz", - "integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==", - "license": "MIT", - "peerDependencies": { - "react": ">= 16 || ^19.0.0-rc" - } - }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -971,6 +965,7 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -981,6 +976,7 @@ "version": "2.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -991,6 +987,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1000,25 +997,20 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@jsdevtools/ono": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", - "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", - "dev": true, - "license": "MIT" - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1071,6 +1063,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1084,6 +1077,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1097,6 +1091,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1110,6 +1105,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1123,6 +1119,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1136,6 +1133,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1149,6 +1147,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1162,6 +1161,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1175,6 +1175,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1188,6 +1189,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1201,6 +1203,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1214,6 +1217,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1227,6 +1231,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1240,6 +1245,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1253,6 +1259,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1266,6 +1273,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1279,6 +1287,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1292,6 +1301,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1305,6 +1315,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1318,6 +1329,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1331,6 +1343,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1344,269 +1357,13 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ] }, - "node_modules/@tailwindcss/node": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.17.tgz", - "integrity": "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==", - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.4", - "enhanced-resolve": "^5.18.3", - "jiti": "^2.6.1", - "lightningcss": "1.30.2", - "magic-string": "^0.30.21", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.17" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.17.tgz", - "integrity": "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==", - "license": "MIT", - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.17", - "@tailwindcss/oxide-darwin-arm64": "4.1.17", - "@tailwindcss/oxide-darwin-x64": "4.1.17", - "@tailwindcss/oxide-freebsd-x64": "4.1.17", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.17", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.17", - "@tailwindcss/oxide-linux-x64-musl": "4.1.17", - "@tailwindcss/oxide-wasm32-wasi": "4.1.17", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.17" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.17.tgz", - "integrity": "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.17.tgz", - "integrity": "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.17.tgz", - "integrity": "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.17.tgz", - "integrity": "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.17.tgz", - "integrity": "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.17.tgz", - "integrity": "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.17.tgz", - "integrity": "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.17.tgz", - "integrity": "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.17.tgz", - "integrity": "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.17.tgz", - "integrity": "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.6.0", - "@emnapi/runtime": "^1.6.0", - "@emnapi/wasi-threads": "^1.1.0", - "@napi-rs/wasm-runtime": "^1.0.7", - "@tybys/wasm-util": "^0.10.1", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.17.tgz", - "integrity": "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.17.tgz", - "integrity": "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.17.tgz", - "integrity": "sha512-4+9w8ZHOiGnpcGI6z1TVVfWaX/koK7fKeSYF3qlYg2xpBtbteP2ddBxiarL+HVgfSJGeK5RIxRQmKm4rTJJAwA==", - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.1.17", - "@tailwindcss/oxide": "4.1.17", - "tailwindcss": "4.1.17" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7" - } - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1656,6 +1413,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, "license": "MIT" }, "node_modules/@types/json-schema": { @@ -1669,7 +1427,7 @@ "version": "24.7.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.0.tgz", "integrity": "sha512-IbKooQVqUBrlzWTi79E8Fw78l8k1RNtlDDNWsFZs7XonuQSJ8oNYfEeclhprUldXISRMLzBpILuKgPlIxm+/Yw==", - "devOptional": true, + "dev": true, "license": "MIT", "peer": true, "dependencies": { @@ -2170,19 +1928,6 @@ "node": ">=6" } }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/caniuse-lite": { "version": "1.0.30001749", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001749.tgz", @@ -2253,16 +1998,6 @@ "node": ">= 0.8" } }, - "node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2277,15 +2012,6 @@ "dev": true, "license": "MIT" }, - "node_modules/cookie": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", - "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2342,15 +2068,6 @@ "node": ">=0.4.0" } }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2372,19 +2089,6 @@ "dev": true, "license": "ISC" }, - "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2434,6 +2138,7 @@ "version": "0.25.10", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", + "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -2834,25 +2539,11 @@ "node": ">= 6" } }, - "node_modules/fs-extra": { - "version": "11.3.2", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", - "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -2957,12 +2648,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -2970,28 +2655,6 @@ "dev": true, "license": "MIT" }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -3118,15 +2781,6 @@ "dev": true, "license": "ISC" }, - "node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3194,19 +2848,6 @@ "node": ">=6" } }, - "node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -3231,255 +2872,6 @@ "node": ">= 0.8.0" } }, - "node_modules/lightningcss": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", - "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.30.2", - "lightningcss-darwin-arm64": "1.30.2", - "lightningcss-darwin-x64": "1.30.2", - "lightningcss-freebsd-x64": "1.30.2", - "lightningcss-linux-arm-gnueabihf": "1.30.2", - "lightningcss-linux-arm64-gnu": "1.30.2", - "lightningcss-linux-arm64-musl": "1.30.2", - "lightningcss-linux-x64-gnu": "1.30.2", - "lightningcss-linux-x64-musl": "1.30.2", - "lightningcss-win32-arm64-msvc": "1.30.2", - "lightningcss-win32-x64-msvc": "1.30.2" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", - "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", - "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", - "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", - "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", - "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", - "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", - "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", - "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", - "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", - "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", - "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -3513,15 +2905,6 @@ "yallist": "^3.0.2" } }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -3589,16 +2972,6 @@ "node": "*" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -3610,6 +2983,7 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, "funding": [ { "type": "github", @@ -3631,13 +3005,6 @@ "dev": true, "license": "MIT" }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "license": "MIT" - }, "node_modules/node-releases": { "version": "2.0.23", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz", @@ -3645,23 +3012,6 @@ "dev": true, "license": "MIT" }, - "node_modules/openapi-typescript-codegen": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/openapi-typescript-codegen/-/openapi-typescript-codegen-0.29.0.tgz", - "integrity": "sha512-/wC42PkD0LGjDTEULa/XiWQbv4E9NwLjwLjsaJ/62yOsoYhwvmBR31kPttn1DzQ2OlGe5stACcF/EIkZk43M6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@apidevtools/json-schema-ref-parser": "^11.5.4", - "camelcase": "^6.3.0", - "commander": "^12.0.0", - "fs-extra": "^11.2.0", - "handlebars": "^4.7.8" - }, - "bin": { - "openapi": "bin/index.js" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -3749,6 +3099,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -3768,6 +3119,7 @@ "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, "funding": [ { "type": "opencollective", @@ -3854,7 +3206,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -3872,44 +3223,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-router": { - "version": "7.9.4", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.4.tgz", - "integrity": "sha512-SD3G8HKviFHg9xj7dNODUKDFgpG4xqD5nhyd0mYoB5iISepuZAvzSr8ywxgxKJ52yRzf/HWtVHc9AWwoTbljvA==", - "license": "MIT", - "dependencies": { - "cookie": "^1.0.1", - "set-cookie-parser": "^2.6.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - } - } - }, - "node_modules/react-router-dom": { - "version": "7.9.4", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.4.tgz", - "integrity": "sha512-f30P6bIkmYvnHHa5Gcu65deIXoA2+r3Eb6PJIAddvsT9aGlchMatJ51GgpU470aSqRRbFX22T70yQNUGuW3DfA==", - "license": "MIT", - "dependencies": { - "react-router": "7.9.4" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" - } - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -3935,6 +3248,7 @@ "version": "4.52.4", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz", "integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==", + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -4012,12 +3326,6 @@ "semver": "bin/semver.js" } }, - "node_modules/set-cookie-parser": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", - "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", - "license": "MIT" - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -4041,20 +3349,11 @@ "node": ">=8" } }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -4086,29 +3385,11 @@ "node": ">=8" } }, - "node_modules/tailwindcss": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz", - "integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==", - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -4125,6 +3406,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -4142,6 +3424,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, "license": "MIT", "peer": true, "engines": { @@ -4229,36 +3512,12 @@ "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/uglify-js": { - "version": "3.19.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", - "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/undici-types": { "version": "7.14.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz", "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } + "license": "MIT" }, "node_modules/update-browserslist-db": { "version": "1.1.3", @@ -4305,6 +3564,7 @@ "version": "7.1.9", "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.9.tgz", "integrity": "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg==", + "dev": true, "license": "MIT", "peer": true, "dependencies": { @@ -4380,6 +3640,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -4397,6 +3658,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, "license": "MIT", "peer": true, "engines": { @@ -4432,13 +3694,6 @@ "node": ">=0.10.0" } }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true, - "license": "MIT" - }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/modules/frontend/package.json b/modules/frontend/package.json index beb2b2a..9fbc7aa 100644 --- a/modules/frontend/package.json +++ b/modules/frontend/package.json @@ -10,13 +10,9 @@ "preview": "vite preview" }, "dependencies": { - "@heroicons/react": "^2.2.0", - "@tailwindcss/vite": "^4.1.17", "axios": "^1.12.2", "react": "^19.1.1", - "react-dom": "^19.1.1", - "react-router-dom": "^7.9.4", - "tailwindcss": "^4.1.17" + "react-dom": "^19.1.1" }, "devDependencies": { "@eslint/js": "^9.36.0", @@ -28,7 +24,6 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.22", "globals": "^16.4.0", - "openapi-typescript-codegen": "^0.29.0", "typescript": "~5.9.3", "typescript-eslint": "^8.45.0", "vite": "^7.1.7" diff --git a/modules/frontend/src/App.tsx b/modules/frontend/src/App.tsx index f67c37e..d9e6fbd 100644 --- a/modules/frontend/src/App.tsx +++ b/modules/frontend/src/App.tsx @@ -1,17 +1,38 @@ -import React from "react"; -import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; -import UserPage from "./pages/UserPage/UserPage"; -import TitlesPage from "./pages/TitlesPage/TitlesPage"; +import React, { useEffect, useState } from "react"; +import { fetchItems } from "./services/api"; +import type { Item } from "./services/api"; +import ItemTemplate from "./components/ItemTemplate"; const App: React.FC = () => { + const [items, setItems] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const getData = async () => { + try { + const data = await fetchItems(); + setItems(data); + } catch (err) { + setError("Failed to fetch items."); + } finally { + setLoading(false); + } + }; + getData(); + }, []); + + if (loading) return
Loading...
; + if (error) return
{error}
; + return ( - - - } /> - } /> - - +
+

Items List

+ {items.map((item) => ( + + ))} +
); }; -export default App; \ No newline at end of file +export default App; diff --git a/modules/frontend/src/api/core/ApiError.ts b/modules/frontend/src/api/core/ApiError.ts deleted file mode 100644 index ec7b16a..0000000 --- a/modules/frontend/src/api/core/ApiError.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* generated using openapi-typescript-codegen -- do not edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -import type { ApiRequestOptions } from './ApiRequestOptions'; -import type { ApiResult } from './ApiResult'; - -export class ApiError extends Error { - public readonly url: string; - public readonly status: number; - public readonly statusText: string; - public readonly body: any; - public readonly request: ApiRequestOptions; - - constructor(request: ApiRequestOptions, response: ApiResult, message: string) { - super(message); - - this.name = 'ApiError'; - this.url = response.url; - this.status = response.status; - this.statusText = response.statusText; - this.body = response.body; - this.request = request; - } -} diff --git a/modules/frontend/src/api/core/ApiRequestOptions.ts b/modules/frontend/src/api/core/ApiRequestOptions.ts deleted file mode 100644 index 93143c3..0000000 --- a/modules/frontend/src/api/core/ApiRequestOptions.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* generated using openapi-typescript-codegen -- do not edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export type ApiRequestOptions = { - readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH'; - readonly url: string; - readonly path?: Record; - readonly cookies?: Record; - readonly headers?: Record; - readonly query?: Record; - readonly formData?: Record; - readonly body?: any; - readonly mediaType?: string; - readonly responseHeader?: string; - readonly errors?: Record; -}; diff --git a/modules/frontend/src/api/core/ApiResult.ts b/modules/frontend/src/api/core/ApiResult.ts deleted file mode 100644 index ee1126e..0000000 --- a/modules/frontend/src/api/core/ApiResult.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* generated using openapi-typescript-codegen -- do not edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export type ApiResult = { - readonly url: string; - readonly ok: boolean; - readonly status: number; - readonly statusText: string; - readonly body: any; -}; diff --git a/modules/frontend/src/api/core/CancelablePromise.ts b/modules/frontend/src/api/core/CancelablePromise.ts deleted file mode 100644 index d70de92..0000000 --- a/modules/frontend/src/api/core/CancelablePromise.ts +++ /dev/null @@ -1,131 +0,0 @@ -/* generated using openapi-typescript-codegen -- do not edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export class CancelError extends Error { - - constructor(message: string) { - super(message); - this.name = 'CancelError'; - } - - public get isCancelled(): boolean { - return true; - } -} - -export interface OnCancel { - readonly isResolved: boolean; - readonly isRejected: boolean; - readonly isCancelled: boolean; - - (cancelHandler: () => void): void; -} - -export class CancelablePromise implements Promise { - #isResolved: boolean; - #isRejected: boolean; - #isCancelled: boolean; - readonly #cancelHandlers: (() => void)[]; - readonly #promise: Promise; - #resolve?: (value: T | PromiseLike) => void; - #reject?: (reason?: any) => void; - - constructor( - executor: ( - resolve: (value: T | PromiseLike) => void, - reject: (reason?: any) => void, - onCancel: OnCancel - ) => void - ) { - this.#isResolved = false; - this.#isRejected = false; - this.#isCancelled = false; - this.#cancelHandlers = []; - this.#promise = new Promise((resolve, reject) => { - this.#resolve = resolve; - this.#reject = reject; - - const onResolve = (value: T | PromiseLike): void => { - if (this.#isResolved || this.#isRejected || this.#isCancelled) { - return; - } - this.#isResolved = true; - if (this.#resolve) this.#resolve(value); - }; - - const onReject = (reason?: any): void => { - if (this.#isResolved || this.#isRejected || this.#isCancelled) { - return; - } - this.#isRejected = true; - if (this.#reject) this.#reject(reason); - }; - - const onCancel = (cancelHandler: () => void): void => { - if (this.#isResolved || this.#isRejected || this.#isCancelled) { - return; - } - this.#cancelHandlers.push(cancelHandler); - }; - - Object.defineProperty(onCancel, 'isResolved', { - get: (): boolean => this.#isResolved, - }); - - Object.defineProperty(onCancel, 'isRejected', { - get: (): boolean => this.#isRejected, - }); - - Object.defineProperty(onCancel, 'isCancelled', { - get: (): boolean => this.#isCancelled, - }); - - return executor(onResolve, onReject, onCancel as OnCancel); - }); - } - - get [Symbol.toStringTag]() { - return "Cancellable Promise"; - } - - public then( - onFulfilled?: ((value: T) => TResult1 | PromiseLike) | null, - onRejected?: ((reason: any) => TResult2 | PromiseLike) | null - ): Promise { - return this.#promise.then(onFulfilled, onRejected); - } - - public catch( - onRejected?: ((reason: any) => TResult | PromiseLike) | null - ): Promise { - return this.#promise.catch(onRejected); - } - - public finally(onFinally?: (() => void) | null): Promise { - return this.#promise.finally(onFinally); - } - - public cancel(): void { - if (this.#isResolved || this.#isRejected || this.#isCancelled) { - return; - } - this.#isCancelled = true; - if (this.#cancelHandlers.length) { - try { - for (const cancelHandler of this.#cancelHandlers) { - cancelHandler(); - } - } catch (error) { - console.warn('Cancellation threw an error', error); - return; - } - } - this.#cancelHandlers.length = 0; - if (this.#reject) this.#reject(new CancelError('Request aborted')); - } - - public get isCancelled(): boolean { - return this.#isCancelled; - } -} diff --git a/modules/frontend/src/api/core/OpenAPI.ts b/modules/frontend/src/api/core/OpenAPI.ts deleted file mode 100644 index 185e5c3..0000000 --- a/modules/frontend/src/api/core/OpenAPI.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* generated using openapi-typescript-codegen -- do not edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -import type { ApiRequestOptions } from './ApiRequestOptions'; - -type Resolver = (options: ApiRequestOptions) => Promise; -type Headers = Record; - -export type OpenAPIConfig = { - BASE: string; - VERSION: string; - WITH_CREDENTIALS: boolean; - CREDENTIALS: 'include' | 'omit' | 'same-origin'; - TOKEN?: string | Resolver | undefined; - USERNAME?: string | Resolver | undefined; - PASSWORD?: string | Resolver | undefined; - HEADERS?: Headers | Resolver | undefined; - ENCODE_PATH?: ((path: string) => string) | undefined; -}; - -export const OpenAPI: OpenAPIConfig = { - BASE: '/api/v1', - VERSION: '1.0.0', - WITH_CREDENTIALS: false, - CREDENTIALS: 'include', - TOKEN: undefined, - USERNAME: undefined, - PASSWORD: undefined, - HEADERS: undefined, - ENCODE_PATH: undefined, -}; diff --git a/modules/frontend/src/api/core/request.ts b/modules/frontend/src/api/core/request.ts deleted file mode 100644 index 1dc6fef..0000000 --- a/modules/frontend/src/api/core/request.ts +++ /dev/null @@ -1,323 +0,0 @@ -/* generated using openapi-typescript-codegen -- do not edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -import axios from 'axios'; -import type { AxiosError, AxiosRequestConfig, AxiosResponse, AxiosInstance } from 'axios'; -import FormData from 'form-data'; - -import { ApiError } from './ApiError'; -import type { ApiRequestOptions } from './ApiRequestOptions'; -import type { ApiResult } from './ApiResult'; -import { CancelablePromise } from './CancelablePromise'; -import type { OnCancel } from './CancelablePromise'; -import type { OpenAPIConfig } from './OpenAPI'; - -export const isDefined = (value: T | null | undefined): value is Exclude => { - return value !== undefined && value !== null; -}; - -export const isString = (value: any): value is string => { - return typeof value === 'string'; -}; - -export const isStringWithValue = (value: any): value is string => { - return isString(value) && value !== ''; -}; - -export const isBlob = (value: any): value is Blob => { - return ( - typeof value === 'object' && - typeof value.type === 'string' && - typeof value.stream === 'function' && - typeof value.arrayBuffer === 'function' && - typeof value.constructor === 'function' && - typeof value.constructor.name === 'string' && - /^(Blob|File)$/.test(value.constructor.name) && - /^(Blob|File)$/.test(value[Symbol.toStringTag]) - ); -}; - -export const isFormData = (value: any): value is FormData => { - return value instanceof FormData; -}; - -export const isSuccess = (status: number): boolean => { - return status >= 200 && status < 300; -}; - -export const base64 = (str: string): string => { - try { - return btoa(str); - } catch (err) { - // @ts-ignore - return Buffer.from(str).toString('base64'); - } -}; - -export const getQueryString = (params: Record): string => { - const qs: string[] = []; - - const append = (key: string, value: any) => { - qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`); - }; - - const process = (key: string, value: any) => { - if (isDefined(value)) { - if (Array.isArray(value)) { - value.forEach(v => { - process(key, v); - }); - } else if (typeof value === 'object') { - Object.entries(value).forEach(([k, v]) => { - process(`${key}[${k}]`, v); - }); - } else { - append(key, value); - } - } - }; - - Object.entries(params).forEach(([key, value]) => { - process(key, value); - }); - - if (qs.length > 0) { - return `?${qs.join('&')}`; - } - - return ''; -}; - -const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => { - const encoder = config.ENCODE_PATH || encodeURI; - - const path = options.url - .replace('{api-version}', config.VERSION) - .replace(/{(.*?)}/g, (substring: string, group: string) => { - if (options.path?.hasOwnProperty(group)) { - return encoder(String(options.path[group])); - } - return substring; - }); - - const url = `${config.BASE}${path}`; - if (options.query) { - return `${url}${getQueryString(options.query)}`; - } - return url; -}; - -export const getFormData = (options: ApiRequestOptions): FormData | undefined => { - if (options.formData) { - const formData = new FormData(); - - const process = (key: string, value: any) => { - if (isString(value) || isBlob(value)) { - formData.append(key, value); - } else { - formData.append(key, JSON.stringify(value)); - } - }; - - Object.entries(options.formData) - .filter(([_, value]) => isDefined(value)) - .forEach(([key, value]) => { - if (Array.isArray(value)) { - value.forEach(v => process(key, v)); - } else { - process(key, value); - } - }); - - return formData; - } - return undefined; -}; - -type Resolver = (options: ApiRequestOptions) => Promise; - -export const resolve = async (options: ApiRequestOptions, resolver?: T | Resolver): Promise => { - if (typeof resolver === 'function') { - return (resolver as Resolver)(options); - } - return resolver; -}; - -export const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOptions, formData?: FormData): Promise> => { - const [token, username, password, additionalHeaders] = await Promise.all([ - resolve(options, config.TOKEN), - resolve(options, config.USERNAME), - resolve(options, config.PASSWORD), - resolve(options, config.HEADERS), - ]); - - const formHeaders = typeof formData?.getHeaders === 'function' && formData?.getHeaders() || {} - - const headers = Object.entries({ - Accept: 'application/json', - ...additionalHeaders, - ...options.headers, - ...formHeaders, - }) - .filter(([_, value]) => isDefined(value)) - .reduce((headers, [key, value]) => ({ - ...headers, - [key]: String(value), - }), {} as Record); - - if (isStringWithValue(token)) { - headers['Authorization'] = `Bearer ${token}`; - } - - if (isStringWithValue(username) && isStringWithValue(password)) { - const credentials = base64(`${username}:${password}`); - headers['Authorization'] = `Basic ${credentials}`; - } - - if (options.body !== undefined) { - if (options.mediaType) { - headers['Content-Type'] = options.mediaType; - } else if (isBlob(options.body)) { - headers['Content-Type'] = options.body.type || 'application/octet-stream'; - } else if (isString(options.body)) { - headers['Content-Type'] = 'text/plain'; - } else if (!isFormData(options.body)) { - headers['Content-Type'] = 'application/json'; - } - } - - return headers; -}; - -export const getRequestBody = (options: ApiRequestOptions): any => { - if (options.body) { - return options.body; - } - return undefined; -}; - -export const sendRequest = async ( - config: OpenAPIConfig, - options: ApiRequestOptions, - url: string, - body: any, - formData: FormData | undefined, - headers: Record, - onCancel: OnCancel, - axiosClient: AxiosInstance -): Promise> => { - const source = axios.CancelToken.source(); - - const requestConfig: AxiosRequestConfig = { - url, - headers, - data: body ?? formData, - method: options.method, - withCredentials: config.WITH_CREDENTIALS, - withXSRFToken: config.CREDENTIALS === 'include' ? config.WITH_CREDENTIALS : false, - cancelToken: source.token, - }; - - onCancel(() => source.cancel('The user aborted a request.')); - - try { - return await axiosClient.request(requestConfig); - } catch (error) { - const axiosError = error as AxiosError; - if (axiosError.response) { - return axiosError.response; - } - throw error; - } -}; - -export const getResponseHeader = (response: AxiosResponse, responseHeader?: string): string | undefined => { - if (responseHeader) { - const content = response.headers[responseHeader]; - if (isString(content)) { - return content; - } - } - return undefined; -}; - -export const getResponseBody = (response: AxiosResponse): any => { - if (response.status !== 204) { - return response.data; - } - return undefined; -}; - -export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => { - const errors: Record = { - 400: 'Bad Request', - 401: 'Unauthorized', - 403: 'Forbidden', - 404: 'Not Found', - 500: 'Internal Server Error', - 502: 'Bad Gateway', - 503: 'Service Unavailable', - ...options.errors, - } - - const error = errors[result.status]; - if (error) { - throw new ApiError(options, result, error); - } - - if (!result.ok) { - const errorStatus = result.status ?? 'unknown'; - const errorStatusText = result.statusText ?? 'unknown'; - const errorBody = (() => { - try { - return JSON.stringify(result.body, null, 2); - } catch (e) { - return undefined; - } - })(); - - throw new ApiError(options, result, - `Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}` - ); - } -}; - -/** - * Request method - * @param config The OpenAPI configuration object - * @param options The request options from the service - * @param axiosClient The axios client instance to use - * @returns CancelablePromise - * @throws ApiError - */ -export const request = (config: OpenAPIConfig, options: ApiRequestOptions, axiosClient: AxiosInstance = axios): CancelablePromise => { - return new CancelablePromise(async (resolve, reject, onCancel) => { - try { - const url = getUrl(config, options); - const formData = getFormData(options); - const body = getRequestBody(options); - const headers = await getHeaders(config, options, formData); - - if (!onCancel.isCancelled) { - const response = await sendRequest(config, options, url, body, formData, headers, onCancel, axiosClient); - const responseBody = getResponseBody(response); - const responseHeader = getResponseHeader(response, options.responseHeader); - - const result: ApiResult = { - url, - ok: isSuccess(response.status), - status: response.status, - statusText: response.statusText, - body: responseHeader ?? responseBody, - }; - - catchErrorCodes(options, result); - - resolve(result.body); - } - } catch (error) { - reject(error); - } - }); -}; diff --git a/modules/frontend/src/api/index.ts b/modules/frontend/src/api/index.ts deleted file mode 100644 index 80ae491..0000000 --- a/modules/frontend/src/api/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* generated using openapi-typescript-codegen -- do not edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export { ApiError } from './core/ApiError'; -export { CancelablePromise, CancelError } from './core/CancelablePromise'; -export { OpenAPI } from './core/OpenAPI'; -export type { OpenAPIConfig } from './core/OpenAPI'; - -export type { cursor } from './models/cursor'; -export type { CursorObj } from './models/CursorObj'; -export type { Image } from './models/Image'; -export type { ReleaseSeason } from './models/ReleaseSeason'; -export type { Review } from './models/Review'; -export type { Studio } from './models/Studio'; -export type { Tag } from './models/Tag'; -export type { Tags } from './models/Tags'; -export type { Title } from './models/Title'; -export type { title_sort } from './models/title_sort'; -export type { TitleSort } from './models/TitleSort'; -export type { TitleStatus } from './models/TitleStatus'; -export type { User } from './models/User'; -export type { UserTitle } from './models/UserTitle'; -export type { UserTitleStatus } from './models/UserTitleStatus'; - -export { DefaultService } from './services/DefaultService'; diff --git a/modules/frontend/src/api/models/CursorObj.ts b/modules/frontend/src/api/models/CursorObj.ts deleted file mode 100644 index f54abb1..0000000 --- a/modules/frontend/src/api/models/CursorObj.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* generated using openapi-typescript-codegen -- do not edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export type CursorObj = { - id: number; - param?: string; -}; - diff --git a/modules/frontend/src/api/models/Image.ts b/modules/frontend/src/api/models/Image.ts deleted file mode 100644 index 1317db7..0000000 --- a/modules/frontend/src/api/models/Image.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* generated using openapi-typescript-codegen -- do not edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export type Image = { - id?: number; - storage_type?: string; - image_path?: string; -}; - diff --git a/modules/frontend/src/api/models/ReleaseSeason.ts b/modules/frontend/src/api/models/ReleaseSeason.ts deleted file mode 100644 index ad9f930..0000000 --- a/modules/frontend/src/api/models/ReleaseSeason.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* generated using openapi-typescript-codegen -- do not edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -/** - * Title release season - */ -export type ReleaseSeason = 'winter' | 'spring' | 'summer' | 'fall'; diff --git a/modules/frontend/src/api/models/Review.ts b/modules/frontend/src/api/models/Review.ts deleted file mode 100644 index 9b453b7..0000000 --- a/modules/frontend/src/api/models/Review.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* generated using openapi-typescript-codegen -- do not edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export type Review = Record; diff --git a/modules/frontend/src/api/models/Studio.ts b/modules/frontend/src/api/models/Studio.ts deleted file mode 100644 index 062695a..0000000 --- a/modules/frontend/src/api/models/Studio.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* generated using openapi-typescript-codegen -- do not edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -import type { Image } from './Image'; -export type Studio = { - id: number; - name: string; - poster?: Image; - description?: string; -}; - diff --git a/modules/frontend/src/api/models/Tag.ts b/modules/frontend/src/api/models/Tag.ts deleted file mode 100644 index 665c724..0000000 --- a/modules/frontend/src/api/models/Tag.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* generated using openapi-typescript-codegen -- do not edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -/** - * A localized tag: keys are language codes (ISO 639-1), values are tag names - */ -export type Tag = Record; diff --git a/modules/frontend/src/api/models/Tags.ts b/modules/frontend/src/api/models/Tags.ts deleted file mode 100644 index 748f066..0000000 --- a/modules/frontend/src/api/models/Tags.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* generated using openapi-typescript-codegen -- do not edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -import type { Tag } from './Tag'; -/** - * Array of localized tags - */ -export type Tags = Array; diff --git a/modules/frontend/src/api/models/Title.ts b/modules/frontend/src/api/models/Title.ts deleted file mode 100644 index 4da7aa3..0000000 --- a/modules/frontend/src/api/models/Title.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* generated using openapi-typescript-codegen -- do not edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export type Title = Record; diff --git a/modules/frontend/src/api/models/TitleSort.ts b/modules/frontend/src/api/models/TitleSort.ts deleted file mode 100644 index 1c9385e..0000000 --- a/modules/frontend/src/api/models/TitleSort.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* generated using openapi-typescript-codegen -- do not edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -/** - * Title sort order - */ -export type TitleSort = 'id' | 'year' | 'rating' | 'views'; diff --git a/modules/frontend/src/api/models/TitleStatus.ts b/modules/frontend/src/api/models/TitleStatus.ts deleted file mode 100644 index 72e0261..0000000 --- a/modules/frontend/src/api/models/TitleStatus.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* generated using openapi-typescript-codegen -- do not edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -/** - * Title status - */ -export type TitleStatus = 'finished' | 'ongoing' | 'planned'; diff --git a/modules/frontend/src/api/models/User.ts b/modules/frontend/src/api/models/User.ts deleted file mode 100644 index cd76dbe..0000000 --- a/modules/frontend/src/api/models/User.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* generated using openapi-typescript-codegen -- do not edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export type User = { - /** - * Unique user ID (primary key) - */ - id?: number; - /** - * ID of the user avatar (references images table) - */ - avatar_id?: number; - /** - * User email - */ - mail?: string; - /** - * Username (alphanumeric + _ or -) - */ - nickname: string; - /** - * Display name - */ - disp_name?: string; - /** - * User description - */ - user_desc?: string; - /** - * Timestamp when the user was created - */ - creation_date?: string; -}; - diff --git a/modules/frontend/src/api/models/UserTitle.ts b/modules/frontend/src/api/models/UserTitle.ts deleted file mode 100644 index 26d5ddc..0000000 --- a/modules/frontend/src/api/models/UserTitle.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* generated using openapi-typescript-codegen -- do not edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export type UserTitle = Record; diff --git a/modules/frontend/src/api/models/UserTitleStatus.ts b/modules/frontend/src/api/models/UserTitleStatus.ts deleted file mode 100644 index 0a29626..0000000 --- a/modules/frontend/src/api/models/UserTitleStatus.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* generated using openapi-typescript-codegen -- do not edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -/** - * User's title status - */ -export type UserTitleStatus = 'finished' | 'planned' | 'dropped' | 'in-progress'; diff --git a/modules/frontend/src/api/models/cursor.ts b/modules/frontend/src/api/models/cursor.ts deleted file mode 100644 index 5788e14..0000000 --- a/modules/frontend/src/api/models/cursor.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* generated using openapi-typescript-codegen -- do not edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -export type cursor = string; diff --git a/modules/frontend/src/api/models/title_sort.ts b/modules/frontend/src/api/models/title_sort.ts deleted file mode 100644 index 69b01a7..0000000 --- a/modules/frontend/src/api/models/title_sort.ts +++ /dev/null @@ -1,6 +0,0 @@ -/* generated using openapi-typescript-codegen -- do not edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -import type { TitleSort } from './TitleSort'; -export type title_sort = TitleSort; diff --git a/modules/frontend/src/api/services/DefaultService.ts b/modules/frontend/src/api/services/DefaultService.ts deleted file mode 100644 index 52321b8..0000000 --- a/modules/frontend/src/api/services/DefaultService.ts +++ /dev/null @@ -1,178 +0,0 @@ -/* generated using openapi-typescript-codegen -- do not edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ -import type { CursorObj } from '../models/CursorObj'; -import type { ReleaseSeason } from '../models/ReleaseSeason'; -import type { Title } from '../models/Title'; -import type { TitleSort } from '../models/TitleSort'; -import type { TitleStatus } from '../models/TitleStatus'; -import type { User } from '../models/User'; -import type { UserTitle } from '../models/UserTitle'; -import type { UserTitleStatus } from '../models/UserTitleStatus'; -import type { CancelablePromise } from '../core/CancelablePromise'; -import { OpenAPI } from '../core/OpenAPI'; -import { request as __request } from '../core/request'; -export class DefaultService { - /** - * Get titles - * @param cursor - * @param sort - * @param sortForward - * @param word - * @param status - * @param rating - * @param releaseYear - * @param releaseSeason - * @param limit - * @param offset - * @param fields - * @returns any List of titles with cursor - * @throws ApiError - */ - public static getTitles( - cursor?: string, - sort?: TitleSort, - sortForward: boolean = true, - word?: string, - status?: TitleStatus, - rating?: number, - releaseYear?: number, - releaseSeason?: ReleaseSeason, - limit: number = 10, - offset?: number, - fields: string = 'all', - ): CancelablePromise<{ - /** - * List of titles - */ - data: Array; - cursor: CursorObj; - }> { - return __request(OpenAPI, { - method: 'GET', - url: '/titles', - query: { - 'cursor': cursor, - 'sort': sort, - 'sort_forward': sortForward, - 'word': word, - 'status': status, - 'rating': rating, - 'release_year': releaseYear, - 'release_season': releaseSeason, - 'limit': limit, - 'offset': offset, - 'fields': fields, - }, - errors: { - 400: `Request params are not correct`, - 500: `Unknown server error`, - }, - }); - } - /** - * Get title description - * @param titleId - * @param fields - * @returns Title Title description - * @throws ApiError - */ - public static getTitles1( - titleId: number, - fields: string = 'all', - ): CancelablePromise<Title> { - return __request(OpenAPI, { - method: 'GET', - url: '/titles/{title_id}', - path: { - 'title_id': titleId, - }, - query: { - 'fields': fields, - }, - errors: { - 400: `Request params are not correct`, - 404: `Title not found`, - 500: `Unknown server error`, - }, - }); - } - /** - * Get user info - * @param userId - * @param fields - * @returns User User info - * @throws ApiError - */ - public static getUsers( - userId: string, - fields: string = 'all', - ): CancelablePromise<User> { - return __request(OpenAPI, { - method: 'GET', - url: '/users/{user_id}', - path: { - 'user_id': userId, - }, - query: { - 'fields': fields, - }, - errors: { - 400: `Request params are not correct`, - 404: `User not found`, - 500: `Unknown server error`, - }, - }); - } - /** - * Get user titles - * @param userId - * @param cursor - * @param word - * @param status - * @param watchStatus - * @param rating - * @param releaseYear - * @param releaseSeason - * @param limit - * @param fields - * @returns UserTitle List of user titles - * @throws ApiError - */ - public static getUsersTitles( - userId: string, - cursor?: string, - word?: string, - status?: TitleStatus, - watchStatus?: UserTitleStatus, - rating?: number, - releaseYear?: number, - releaseSeason?: ReleaseSeason, - limit: number = 10, - fields: string = 'all', - ): CancelablePromise<Array<UserTitle>> { - return __request(OpenAPI, { - method: 'GET', - url: '/users/{user_id}/titles/', - path: { - 'user_id': userId, - }, - query: { - 'cursor': cursor, - 'word': word, - 'status': status, - 'watch_status': watchStatus, - 'rating': rating, - 'release_year': releaseYear, - 'release_season': releaseSeason, - 'limit': limit, - 'fields': fields, - }, - errors: { - 400: `Request params are not correct`, - 500: `Unknown server error`, - }, - }); - } -} diff --git a/modules/frontend/src/components/ListView/ListView.tsx b/modules/frontend/src/components/ListView/ListView.tsx deleted file mode 100644 index e0e8ab9..0000000 --- a/modules/frontend/src/components/ListView/ListView.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import React, { useState, useEffect } from "react"; -import { Squares2X2Icon, Bars3Icon } from "@heroicons/react/24/solid"; -import type { CursorObj } from "../../api"; - -export type ListViewProps<T> = { - fetchItems: (cursor: string, limit: number) => Promise<{ items: T[]; cursor: CursorObj}>; - renderItem: (item: T, layout: "square" | "horizontal") => React.ReactNode; - pageSize?: number; - searchPlaceholder?: string; - setSearch: any; -}; - -export function ListView<T>({ - fetchItems, - renderItem, - pageSize = 20, - searchPlaceholder = "Search...", -}: ListViewProps<T>) { - const [items, setItems] = useState<T[]>([]); - const [cursorObj, setCursorObj] = useState<CursorObj | undefined>(undefined); - const [loading, setLoading] = useState(true); - const [loadingMore, setLoadingMore] = useState(false); - const [search, setSearch] = useState(""); - const [layout, setLayout] = useState<"square" | "horizontal">("horizontal"); - const [error, setError] = useState<string | null>(null); - - const loadItems = async (reset: boolean = false) => { - try { - if (reset) { - setLoading(true); - setCursorObj(undefined); - } else { - setLoadingMore(true); - } - - const cursorStr = cursorObj ? btoa(JSON.stringify(cursorObj)) : "" - console.log("encoded cursor: " + cursorStr) - - const result = await fetchItems(cursorStr, pageSize); - - if (reset) setItems(result.items); - else setItems(prev => [...prev, ...result.items]); - - setCursorObj(result.cursor); - setError(null); - } catch (err: any) { - console.error(err); - setError("Failed to fetch items."); - } finally { - setLoading(false); - setLoadingMore(false); - } - }; - - useEffect(() => { - loadItems(true); - }, [search]); - - return ( - <div className="w-full min-h-screen bg-gray-50 p-6 text-black flex flex-col items-center"> - <div className="w-full sm:w-4/5 flex gap-4 mb-8"> - <input - type="text" - placeholder={searchPlaceholder} - // value={search} - onChange={e => setSearch(e.target.value)} - className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 text-black" - /> - <button - className="p-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition" - onClick={() => - setLayout(prev => (prev === "square" ? "horizontal" : "square")) - }> - {layout === "square" ? <Squares2X2Icon className="w-6 h-6" /> : <Bars3Icon className="w-6 h-6" />} - </button> - </div> - - {error && <div className="text-red-600 mb-6 font-medium">{error}</div>} - - <div - className={`w-full sm:w-4/5 grid gap-6 ${ - layout === "square" ? "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4" : "grid-cols-1" - }`} - > - {items.map(item => renderItem(item, layout))} - </div> - - {cursorObj && ( - <div className="mt-8 flex justify-center w-full sm:w-4/5"> - <button - className="px-6 py-3 bg-blue-600 text-white font-semibold rounded-lg hover:bg-blue-700 transition disabled:opacity-50 disabled:cursor-not-allowed" - onClick={() => loadItems(false)} - disabled={loadingMore} - > - {loadingMore ? "Loading..." : "Load More"} - </button> - </div> - )} - - {loading && <div className="mt-20 font-medium">Loading...</div>} - </div> - ); -} diff --git a/modules/frontend/src/components/cards/TitleCardHorizontal.tsx b/modules/frontend/src/components/cards/TitleCardHorizontal.tsx deleted file mode 100644 index cde6037..0000000 --- a/modules/frontend/src/components/cards/TitleCardHorizontal.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { Title } from "../../api/models/Title"; - -export function TitleCardHorizontal({ title }: { title: Title }) { - return ( - <div style={{ - display: "flex", - gap: 12, - padding: 12, - border: "1px solid #ddd", - borderRadius: 8 - }}> - {title.poster?.image_path && ( - <img src={title.poster.image_path} width={80} /> - )} - <div> - <h3>{title.title_names["en"]}</h3> - <p>{title.release_year} · {title.release_season} · Rating: {title.rating}</p> - <p>Status: {title.title_status}</p> - </div> - </div> - ); -} diff --git a/modules/frontend/src/components/cards/TitleCardSquare.tsx b/modules/frontend/src/components/cards/TitleCardSquare.tsx deleted file mode 100644 index e21c258..0000000 --- a/modules/frontend/src/components/cards/TitleCardSquare.tsx +++ /dev/null @@ -1,22 +0,0 @@ -// TitleCardSquare.tsx -import type { Title } from "../../api/models/Title"; - -export function TitleCardSquare({ title }: { title: Title }) { - return ( - <div style={{ - width: 160, - border: "1px solid #ddd", - padding: 8, - borderRadius: 8, - textAlign: "center" - }}> - {title.poster?.image_path && ( - <img src={title.poster.image_path} width={140} /> - )} - <div> - <h4>{title.title_names["en"]}</h4> - <small>{title.release_year} • {title.rating}</small> - </div> - </div> - ); -} diff --git a/modules/frontend/src/index.css b/modules/frontend/src/index.css index e20de02..08a3ac9 100644 --- a/modules/frontend/src/index.css +++ b/modules/frontend/src/index.css @@ -1,8 +1,68 @@ -@import "tailwindcss"; +:root { + font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; -html, body, #root { + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { margin: 0; - padding: 0; - width: 100%; - height: 100%; -} \ No newline at end of file + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/modules/frontend/src/pages/TitlePage/TitlePage.module.css b/modules/frontend/src/pages/TitlePage/TitlePage.module.css deleted file mode 100644 index e69de29..0000000 diff --git a/modules/frontend/src/pages/TitlePage/TitlePage.tsx b/modules/frontend/src/pages/TitlePage/TitlePage.tsx deleted file mode 100644 index 7fe9de7..0000000 --- a/modules/frontend/src/pages/TitlePage/TitlePage.tsx +++ /dev/null @@ -1,64 +0,0 @@ -// import React, { useEffect, useState } from "react"; -// import { useParams } from "react-router-dom"; -// import { DefaultService } from "../../api/services/DefaultService"; -// import type { User } from "../../api/models/User"; -// import styles from "./UserPage.module.css"; - -// const UserPage: React.FC = () => { -// const { id } = useParams<{ id: string }>(); -// const [user, setUser] = useState<User | null>(null); -// const [loading, setLoading] = useState(true); -// const [error, setError] = useState<string | null>(null); - -// useEffect(() => { -// if (!id) return; - -// const getTitleInfo = async () => { -// try { -// const userInfo = await DefaultService.getTitle(id, "all"); -// setUser(userInfo); -// } catch (err) { -// console.error(err); -// setError("Failed to fetch user info."); -// } finally { -// setLoading(false); -// } -// }; -// getTitleInfo(); -// }, [id]); - -// if (loading) return <div className={styles.loader}>Loading...</div>; -// if (error) return <div className={styles.error}>{error}</div>; -// if (!user) return <div className={styles.error}>User not found.</div>; - -// return ( -// <div className={styles.container}> -// <div className={styles.card}> -// <div className={styles.avatar}> -// {user.avatar_id ? ( -// <img -// src={`/images/${user.avatar_id}.png`} -// alt="User Avatar" -// className={styles.avatarImg} -// /> -// ) : ( -// <div className={styles.avatarPlaceholder}> -// {user.disp_name?.[0] || "U"} -// </div> -// )} -// </div> - -// <div className={styles.info}> -// <h1 className={styles.name}>{user.disp_name || user.nickname}</h1> -// <p className={styles.nickname}>@{user.nickname}</p> -// {user.user_desc && <p className={styles.desc}>{user.user_desc}</p>} -// <p className={styles.created}> -// Joined: {new Date(user.creation_date).toLocaleDateString()} -// </p> -// </div> -// </div> -// </div> -// ); -// }; - -// export default UserPage; diff --git a/modules/frontend/src/pages/TitlesPage/TitlesPage.module.css b/modules/frontend/src/pages/TitlesPage/TitlesPage.module.css deleted file mode 100644 index f1d8c73..0000000 --- a/modules/frontend/src/pages/TitlesPage/TitlesPage.module.css +++ /dev/null @@ -1 +0,0 @@ -@import "tailwindcss"; diff --git a/modules/frontend/src/pages/TitlesPage/TitlesPage.tsx b/modules/frontend/src/pages/TitlesPage/TitlesPage.tsx deleted file mode 100644 index b59f737..0000000 --- a/modules/frontend/src/pages/TitlesPage/TitlesPage.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { ListView } from "../../components/ListView/ListView"; -import { DefaultService } from "../../api/services/DefaultService"; -import { TitleCardSquare } from "../../components/cards/TitleCardSquare"; -import { TitleCardHorizontal } from "../../components/cards/TitleCardHorizontal"; -import type { Title } from "../../api"; -import { useState, useEffect } from "react"; - -const PAGE_SIZE = 20; -export default function TitlesPage() { - const [search, setSearch] = useState(""); - - const loadTitles = async (cursor: string, limit: number) => { - const result = await DefaultService.getTitles( - cursor, - undefined, - true, - search, - undefined, - undefined, - undefined, - undefined, - limit, - undefined, - 'all' - ); - - return { - items: result.data ?? [], - cursor: result.cursor ?? null, - }; - }; - - return ( - <div className="w-full min-h-screen bg-gray-50 p-6 text-black flex flex-col items-center"> - - <h1 className="text-4xl font-bold mb-6 text-center">Titles</h1> - - <ListView<Title> - pageSize={PAGE_SIZE} - fetchItems={loadTitles} - searchPlaceholder="Search titles..." - renderItem={(title, layout) => - layout === "square" - ? <TitleCardSquare title={title} /> - : <TitleCardHorizontal title={title} /> - } - setSearch={setSearch} - /> - </div> - ); -} - diff --git a/modules/frontend/src/pages/UserPage/UserPage.module.css b/modules/frontend/src/pages/UserPage/UserPage.module.css deleted file mode 100644 index 7f350c8..0000000 --- a/modules/frontend/src/pages/UserPage/UserPage.module.css +++ /dev/null @@ -1,103 +0,0 @@ -body, -html { - width: 100%; - margin: 0; - background-color: #777; - color: #fff; -} - -html, -body, -#root { - height: 100%; -} - -.header { - width: 100vw; - padding: 30px 40px; - background: #f7f7f7; - display: flex; - align-items: center; - gap: 25px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); - border-bottom: 1px solid #e5e5e5; - color: #000000; -} - -.avatarWrapper { - width: 120px; - height: 120px; - min-width: 120px; - border-radius: 50%; - overflow: hidden; - display: flex; - align-items: center; - justify-content: center; - background: #ddd; -} - -.avatarImg { - width: 100%; - height: 100%; - object-fit: cover; -} - -.avatarPlaceholder { - width: 100%; - height: 100%; - border-radius: 50%; - background: #ccc; - font-size: 42px; - font-weight: bold; - color: #555; - display: flex; - align-items: center; - justify-content: center; -} - -.userInfo { - display: flex; - flex-direction: column; -} - -.name { - font-size: 32px; - font-weight: 700; - margin: 0; -} - -.nickname { - font-size: 18px; - color: #666; - margin-top: 6px; -} - -.container { - max-width: 100vw; - width: 100%; - position: absolute; - top: 0%; - /* margin: 25px auto; */ - /* padding: 0 20px; */ -} - -.content { - margin-top: 20px; -} - -.desc { - font-size: 18px; - margin-bottom: 10px; -} - -.created { - font-size: 16px; - color: #888; -} - -.loader, -.error { - text-align: center; - margin-top: 40px; - font-size: 18px; -} diff --git a/modules/frontend/src/pages/UserPage/UserPage.tsx b/modules/frontend/src/pages/UserPage/UserPage.tsx deleted file mode 100644 index 52c5574..0000000 --- a/modules/frontend/src/pages/UserPage/UserPage.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { useParams } from "react-router-dom"; // <-- import -import { DefaultService } from "../../api/services/DefaultService"; -import type { User } from "../../api/models/User"; -import styles from "./UserPage.module.css"; - -const UserPage: React.FC = () => { - const { id } = useParams<{ id: string }>(); // <-- get user id from URL - const [user, setUser] = useState<User | null>(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState<string | null>(null); - - useEffect(() => { - if (!id) return; - - const getUserInfo = async () => { - try { - const userInfo = await DefaultService.getUsers(id, "all"); // <-- use dynamic id - setUser(userInfo); - } catch (err) { - console.error(err); - setError("Failed to fetch user info."); - } finally { - setLoading(false); - } - }; - getUserInfo(); - }, [id]); - - if (loading) return <div className={styles.loader}>Loading...</div>; - if (error) return <div className={styles.error}>{error}</div>; - if (!user) return <div className={styles.error}>User not found.</div>; - - return ( - <div className={styles.container}> - <div className={styles.header}> - <div className={styles.avatarWrapper}> - {user.avatar_id ? ( - <img - src={`/images/${user.avatar_id}.png`} - alt="User Avatar" - className={styles.avatarImg} - /> - ) : ( - <div className={styles.avatarPlaceholder}> - {user.disp_name?.[0] || "U"} - </div> - )} - </div> - - <div className={styles.userInfo}> - <h1 className={styles.name}>{user.disp_name || user.nickname}</h1> - <p className={styles.nickname}>@{user.nickname}</p> - {/* <p className={styles.created}> - Joined: {new Date(user.creation_date).toLocaleDateString()} - </p> */} - </div> - - <div className={styles.content}> - {user.user_desc && <p className={styles.desc}>{user.user_desc}</p>} - </div> - </div> - </div> - ); -}; - -export default UserPage; diff --git a/modules/frontend/src/types/list.ts b/modules/frontend/src/types/list.ts deleted file mode 100644 index 582da39..0000000 --- a/modules/frontend/src/types/list.ts +++ /dev/null @@ -1,12 +0,0 @@ -export interface PaginatedResult<TItem> { - items: TItem[]; - nextCursor?: string; -} - -export interface FetchParams { - search: string; - cursor?: string; -} - -export type FetchFunction<TItem> = - (params: FetchParams) => Promise<PaginatedResult<TItem>>; diff --git a/modules/frontend/vite.config.ts b/modules/frontend/vite.config.ts index 6c261e6..4cfbdd0 100644 --- a/modules/frontend/vite.config.ts +++ b/modules/frontend/vite.config.ts @@ -1,13 +1,9 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' -import tailwindcss from '@tailwindcss/vite' // https://vite.dev/config/ export default defineConfig({ - plugins: [ - react(), - tailwindcss() - ], + plugins: [react()], server: { host: '127.0.0.1', port: 8083, diff --git a/modules/backend/config.toml b/modules/server/config.toml similarity index 100% rename from modules/backend/config.toml rename to modules/server/config.toml diff --git a/go.mod b/modules/server/go.mod similarity index 66% rename from go.mod rename to modules/server/go.mod index 7c34aeb..cbdfb8b 100644 --- a/go.mod +++ b/modules/server/go.mod @@ -1,37 +1,26 @@ -module nyanimedb +module nyanimedb-server go 1.25.0 require ( - github.com/gin-contrib/cors v1.7.6 - github.com/gin-gonic/gin v1.11.0 - github.com/jackc/pgx/v5 v5.7.6 - github.com/oapi-codegen/runtime v1.1.2 - github.com/pelletier/go-toml/v2 v2.2.4 - golang.org/x/crypto v0.40.0 -) - -require ( - github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/bytedance/sonic v1.14.0 // indirect github.com/bytedance/sonic/loader v0.3.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect - github.com/gabriel-vasile/mimetype v1.4.9 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/gin-contrib/sse v1.1.0 // indirect + github.com/gin-gonic/gin v1.11.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.27.0 // indirect - github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-yaml v1.18.0 // indirect - github.com/google/uuid v1.5.0 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/quic-go/qpack v0.5.1 // indirect github.com/quic-go/quic-go v0.54.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect @@ -39,6 +28,7 @@ require ( github.com/ugorji/go/codec v1.3.0 // indirect go.uber.org/mock v0.5.0 // indirect golang.org/x/arch v0.20.0 // indirect + golang.org/x/crypto v0.40.0 // indirect golang.org/x/mod v0.25.0 // indirect golang.org/x/net v0.42.0 // indirect golang.org/x/sync v0.16.0 // indirect diff --git a/go.sum b/modules/server/go.sum similarity index 67% rename from go.sum rename to modules/server/go.sum index 121ca40..cb0d745 100644 --- a/go.sum +++ b/modules/server/go.sum @@ -1,7 +1,3 @@ -github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= -github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= -github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= -github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= @@ -9,60 +5,38 @@ github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFos github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= -github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= -github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY= -github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= -github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= -github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk= -github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= -github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= -github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI= -github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= @@ -70,7 +44,6 @@ github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQB github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -79,8 +52,6 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= @@ -97,6 +68,7 @@ golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= @@ -109,5 +81,4 @@ google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7I google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/modules/backend/helpers.go b/modules/server/helpers.go similarity index 100% rename from modules/backend/helpers.go rename to modules/server/helpers.go diff --git a/modules/server/main.go b/modules/server/main.go new file mode 100644 index 0000000..775540b --- /dev/null +++ b/modules/server/main.go @@ -0,0 +1,78 @@ +package main + +import ( + "fmt" + "net/http" + "os" + "reflect" + + "github.com/gin-gonic/gin" + "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) + } + + r := gin.Default() + + r.LoadHTMLGlob("templates/*") + + r.GET("/", func(c *gin.Context) { + c.HTML(http.StatusOK, "index.html", gin.H{ + "title": "Welcome Page", + "message": "Hello, Gin with HTML templates!", + }) + }) + + r.Run(":8080") +} + +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/server/templates/index.html b/modules/server/templates/index.html new file mode 100644 index 0000000..9f7bd75 --- /dev/null +++ b/modules/server/templates/index.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <title>{{ .title }} + + +

{{ .message }}

+ + \ No newline at end of file diff --git a/modules/server/types.go b/modules/server/types.go new file mode 100644 index 0000000..539babf --- /dev/null +++ b/modules/server/types.go @@ -0,0 +1,6 @@ +package main + +type Config struct { + Mode string + LogLevel string `toml:"LogLevel" env:"LOG_LEVEL"` +} diff --git a/sql/db.go b/sql/db.go deleted file mode 100644 index 7a56507..0000000 --- a/sql/db.go +++ /dev/null @@ -1,32 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 - -package sqlc - -import ( - "context" - - "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgconn" -) - -type DBTX interface { - Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) - Query(context.Context, string, ...interface{}) (pgx.Rows, error) - QueryRow(context.Context, string, ...interface{}) pgx.Row -} - -func New(db DBTX) *Queries { - return &Queries{db: db} -} - -type Queries struct { - db DBTX -} - -func (q *Queries) WithTx(tx pgx.Tx) *Queries { - return &Queries{ - db: tx, - } -} diff --git a/sql/migrations/000001_init.down.sql b/sql/migrations/000001_init.down.sql deleted file mode 100644 index dc52d23..0000000 --- a/sql/migrations/000001_init.down.sql +++ /dev/null @@ -1,20 +0,0 @@ -DROP TRIGGER IF EXISTS trg_update_title_rating ON usertitles; -DROP TRIGGER IF EXISTS trg_notify_new_signal ON signals; - -DROP FUNCTION IF EXISTS update_title_rating(); -DROP FUNCTION IF EXISTS notify_new_signal(); - -DROP TABLE IF EXISTS signals; -DROP TABLE IF EXISTS title_tags; -DROP TABLE IF EXISTS usertitles; -DROP TABLE IF EXISTS titles; -DROP TABLE IF EXISTS studios; -DROP TABLE IF EXISTS users; -DROP TABLE IF EXISTS images; -DROP TABLE IF EXISTS tags; -DROP TABLE IF EXISTS providers; - -DROP TYPE IF EXISTS release_season_t; -DROP TYPE IF EXISTS title_status_t; -DROP TYPE IF EXISTS storage_type_t; -DROP TYPE IF EXISTS usertitle_status_t; \ No newline at end of file diff --git a/sql/migrations/000001_init.up.sql b/sql/migrations/000001_init.up.sql deleted file mode 100644 index 49cca3d..0000000 --- a/sql/migrations/000001_init.up.sql +++ /dev/null @@ -1,172 +0,0 @@ --- TODO: --- maybe jsonb constraints --- clean unused images -CREATE TYPE usertitle_status_t AS ENUM ('finished', 'planned', 'dropped', 'in-progress'); -CREATE TYPE storage_type_t AS ENUM ('local', 's3'); -CREATE TYPE title_status_t AS ENUM ('finished', 'ongoing', 'planned'); -CREATE TYPE release_season_t AS ENUM ('winter', 'spring', 'summer', 'fall'); - -CREATE TABLE providers ( - id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, - provider_name text NOT NULL, - credentials jsonb -); - -CREATE TABLE tags ( - id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, - -- example: { "ru": "Сёдзё", "en": "Shojo", "jp": "少女"} - tag_names jsonb NOT NULL -); - -CREATE TABLE images ( - id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, - storage_type storage_type_t NOT NULL, - image_path text UNIQUE NOT NULL -); - -CREATE TABLE reviews ( - id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, - data text NOT NULL, - rating int CHECK (rating >= 0 AND rating <= 10), - user_id bigint REFERENCES users (id), - title_id bigint REFERENCES titles (id), - created_at timestamptz DEFAULT NOW() -); - -CREATE TABLE review_images ( - PRIMARY KEY (review_id, image_id), - review_id bigint NOT NULL REFERENCES reviews(id) ON DELETE CASCADE, - image_id bigint NOT NULL REFERENCES images(id) ON DELETE CASCADE -); - -CREATE TABLE users ( - id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, - avatar_id bigint REFERENCES images (id), - passhash text NOT NULL, - mail text CHECK (mail ~ '^[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+$'), - nickname text UNIQUE NOT NULL CHECK (nickname ~ '^[a-zA-Z0-9_-]{3,}$'), - disp_name text, - user_desc text, - creation_date timestamptz NOT NULL, - last_login timestamptz -); - -CREATE TABLE studios ( - id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, - studio_name text NOT NULL UNIQUE, - illust_id bigint REFERENCES images (id), - studio_desc text -); - -CREATE TABLE titles ( - // TODO: anime type (film, season etc) - id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, - -- example {"ru": ["Атака титанов", "Атака Титанов"],"en": ["Attack on Titan", "AoT"],"ja": ["進撃の巨人", "しんげきのきょじん"]} - title_names jsonb NOT NULL, - studio_id bigint NOT NULL REFERENCES studios (id), - poster_id bigint REFERENCES images (id), - title_status title_status_t NOT NULL, - rating float CHECK (rating >= 0 AND rating <= 10), - rating_count int CHECK (rating_count >= 0), - release_year int CHECK (release_year >= 1900), - release_season release_season_t, - season int CHECK (season >= 0), - episodes_aired int CHECK (episodes_aired >= 0), - episodes_all int CHECK (episodes_all >= 0), - -- example { "1": "50.50", "2": "23.23"} - episodes_len jsonb, - CHECK ((episodes_aired IS NULL AND episodes_all IS NULL) - OR (episodes_aired IS NOT NULL AND episodes_all IS NOT NULL - AND episodes_aired <= episodes_all)) -); - -CREATE TABLE usertitles ( - PRIMARY KEY (user_id, title_id), - user_id bigint NOT NULL REFERENCES users (id), - title_id bigint NOT NULL REFERENCES titles (id), - status usertitle_status_t NOT NULL, - rate int CHECK (rate > 0 AND rate <= 10), - review_id bigint REFERENCES reviews (id), - ctime timestamptz - -- // TODO: series status -); - -CREATE TABLE title_tags ( - PRIMARY KEY (title_id, tag_id), - title_id bigint NOT NULL REFERENCES titles (id), - tag_id bigint NOT NULL REFERENCES tags (id) -); - -CREATE TABLE signals ( - id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, - title_id bigint REFERENCES titles (id), - raw_data jsonb NOT NULL, - provider_id bigint NOT NULL REFERENCES providers (id), - pending boolean NOT NULL -); - -CREATE TABLE external_ids ( - user_id bigint NOT NULL REFERENCES users (id), - service_id bigint REFERENCES external_services (id), - external_id text NOT NULL -); - -CREATE TABLE external_services ( - id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, - name text UNIQUE NOT NULL -); - --- Functions -CREATE OR REPLACE FUNCTION update_title_rating() -RETURNS TRIGGER AS $$ -BEGIN - IF (TG_OP = 'INSERT') OR (TG_OP = 'UPDATE' AND NEW.rate IS DISTINCT FROM OLD.rate) THEN - UPDATE titles - SET - rating = sub.avg_rating, - rating_count = sub.rating_count - FROM ( - SELECT - title_id, - AVG(rate)::float AS avg_rating, - COUNT(rate) AS rating_count - FROM usertitles - WHERE title_id = NEW.title_id AND rate IS NOT NULL - GROUP BY title_id - ) AS sub - WHERE titles.id = sub.title_id; - END IF; - - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION notify_new_signal() -RETURNS TRIGGER AS $$ -DECLARE - payload JSON; -BEGIN - payload := json_build_object( - 'signal_id', NEW.id, - 'title_id', NEW.title_id, - 'provider_id', NEW.provider_id, - 'pending', NEW.pending, - 'timestamp', NOW() - ); - PERFORM pg_notify('new_signal', payload::text); - - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - --- Triggers - -CREATE TRIGGER trg_update_title_rating -AFTER INSERT OR UPDATE OF rate ON usertitles -FOR EACH ROW -EXECUTE FUNCTION update_title_rating(); - -CREATE TRIGGER trg_notify_new_signal -AFTER INSERT ON signals -FOR EACH ROW -EXECUTE FUNCTION notify_new_signal(); \ No newline at end of file diff --git a/sql/models.go b/sql/models.go deleted file mode 100644 index a593504..0000000 --- a/sql/models.go +++ /dev/null @@ -1,286 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 - -package sqlc - -import ( - "database/sql/driver" - "fmt" - "time" - - "github.com/jackc/pgx/v5/pgtype" -) - -type ReleaseSeasonT string - -const ( - ReleaseSeasonTWinter ReleaseSeasonT = "winter" - ReleaseSeasonTSpring ReleaseSeasonT = "spring" - ReleaseSeasonTSummer ReleaseSeasonT = "summer" - ReleaseSeasonTFall ReleaseSeasonT = "fall" -) - -func (e *ReleaseSeasonT) Scan(src interface{}) error { - switch s := src.(type) { - case []byte: - *e = ReleaseSeasonT(s) - case string: - *e = ReleaseSeasonT(s) - default: - return fmt.Errorf("unsupported scan type for ReleaseSeasonT: %T", src) - } - return nil -} - -type NullReleaseSeasonT struct { - ReleaseSeasonT ReleaseSeasonT `json:"release_season_t"` - Valid bool `json:"valid"` // Valid is true if ReleaseSeasonT is not NULL -} - -// Scan implements the Scanner interface. -func (ns *NullReleaseSeasonT) Scan(value interface{}) error { - if value == nil { - ns.ReleaseSeasonT, ns.Valid = "", false - return nil - } - ns.Valid = true - return ns.ReleaseSeasonT.Scan(value) -} - -// Value implements the driver Valuer interface. -func (ns NullReleaseSeasonT) Value() (driver.Value, error) { - if !ns.Valid { - return nil, nil - } - return string(ns.ReleaseSeasonT), nil -} - -type StorageTypeT string - -const ( - StorageTypeTLocal StorageTypeT = "local" - StorageTypeTS3 StorageTypeT = "s3" -) - -func (e *StorageTypeT) Scan(src interface{}) error { - switch s := src.(type) { - case []byte: - *e = StorageTypeT(s) - case string: - *e = StorageTypeT(s) - default: - return fmt.Errorf("unsupported scan type for StorageTypeT: %T", src) - } - return nil -} - -type NullStorageTypeT struct { - StorageTypeT StorageTypeT `json:"storage_type_t"` - Valid bool `json:"valid"` // Valid is true if StorageTypeT is not NULL -} - -// Scan implements the Scanner interface. -func (ns *NullStorageTypeT) Scan(value interface{}) error { - if value == nil { - ns.StorageTypeT, ns.Valid = "", false - return nil - } - ns.Valid = true - return ns.StorageTypeT.Scan(value) -} - -// Value implements the driver Valuer interface. -func (ns NullStorageTypeT) Value() (driver.Value, error) { - if !ns.Valid { - return nil, nil - } - return string(ns.StorageTypeT), nil -} - -type TitleStatusT string - -const ( - TitleStatusTFinished TitleStatusT = "finished" - TitleStatusTOngoing TitleStatusT = "ongoing" - TitleStatusTPlanned TitleStatusT = "planned" -) - -func (e *TitleStatusT) Scan(src interface{}) error { - switch s := src.(type) { - case []byte: - *e = TitleStatusT(s) - case string: - *e = TitleStatusT(s) - default: - return fmt.Errorf("unsupported scan type for TitleStatusT: %T", src) - } - return nil -} - -type NullTitleStatusT struct { - TitleStatusT TitleStatusT `json:"title_status_t"` - Valid bool `json:"valid"` // Valid is true if TitleStatusT is not NULL -} - -// Scan implements the Scanner interface. -func (ns *NullTitleStatusT) Scan(value interface{}) error { - if value == nil { - ns.TitleStatusT, ns.Valid = "", false - return nil - } - ns.Valid = true - return ns.TitleStatusT.Scan(value) -} - -// Value implements the driver Valuer interface. -func (ns NullTitleStatusT) Value() (driver.Value, error) { - if !ns.Valid { - return nil, nil - } - return string(ns.TitleStatusT), nil -} - -type UsertitleStatusT string - -const ( - UsertitleStatusTFinished UsertitleStatusT = "finished" - UsertitleStatusTPlanned UsertitleStatusT = "planned" - UsertitleStatusTDropped UsertitleStatusT = "dropped" - UsertitleStatusTInProgress UsertitleStatusT = "in-progress" -) - -func (e *UsertitleStatusT) Scan(src interface{}) error { - switch s := src.(type) { - case []byte: - *e = UsertitleStatusT(s) - case string: - *e = UsertitleStatusT(s) - default: - return fmt.Errorf("unsupported scan type for UsertitleStatusT: %T", src) - } - return nil -} - -type NullUsertitleStatusT struct { - UsertitleStatusT UsertitleStatusT `json:"usertitle_status_t"` - Valid bool `json:"valid"` // Valid is true if UsertitleStatusT is not NULL -} - -// Scan implements the Scanner interface. -func (ns *NullUsertitleStatusT) Scan(value interface{}) error { - if value == nil { - ns.UsertitleStatusT, ns.Valid = "", false - return nil - } - ns.Valid = true - return ns.UsertitleStatusT.Scan(value) -} - -// Value implements the driver Valuer interface. -func (ns NullUsertitleStatusT) Value() (driver.Value, error) { - if !ns.Valid { - return nil, nil - } - return string(ns.UsertitleStatusT), nil -} - -type ExternalID struct { - UserID int64 `json:"user_id"` - ServiceID *int64 `json:"service_id"` - ExternalID string `json:"external_id"` -} - -type ExternalService struct { - ID int64 `json:"id"` - Name string `json:"name"` -} - -type Image struct { - ID int64 `json:"id"` - StorageType StorageTypeT `json:"storage_type"` - ImagePath string `json:"image_path"` -} - -type Provider struct { - ID int64 `json:"id"` - ProviderName string `json:"provider_name"` - Credentials []byte `json:"credentials"` -} - -type Review struct { - ID int64 `json:"id"` - Data string `json:"data"` - Rating *int32 `json:"rating"` - IllustID *int64 `json:"illust_id"` - UserID *int64 `json:"user_id"` - TitleID *int64 `json:"title_id"` - CreatedAt pgtype.Timestamptz `json:"created_at"` -} - -type ReviewImage struct { - ReviewID int64 `json:"review_id"` - ImageID int64 `json:"image_id"` -} - -type Signal struct { - ID int64 `json:"id"` - TitleID *int64 `json:"title_id"` - RawData []byte `json:"raw_data"` - ProviderID int64 `json:"provider_id"` - Pending bool `json:"pending"` -} - -type Studio struct { - ID int64 `json:"id"` - StudioName string `json:"studio_name"` - IllustID *int64 `json:"illust_id"` - StudioDesc *string `json:"studio_desc"` -} - -type Tag struct { - ID int64 `json:"id"` - TagNames []byte `json:"tag_names"` -} - -type Title struct { - ID int64 `json:"id"` - TitleNames []byte `json:"title_names"` - StudioID int64 `json:"studio_id"` - PosterID *int64 `json:"poster_id"` - TitleStatus TitleStatusT `json:"title_status"` - Rating *float64 `json:"rating"` - RatingCount *int32 `json:"rating_count"` - ReleaseYear *int32 `json:"release_year"` - ReleaseSeason *ReleaseSeasonT `json:"release_season"` - Season *int32 `json:"season"` - EpisodesAired *int32 `json:"episodes_aired"` - EpisodesAll *int32 `json:"episodes_all"` - EpisodesLen []byte `json:"episodes_len"` -} - -type TitleTag struct { - TitleID int64 `json:"title_id"` - TagID int64 `json:"tag_id"` -} - -type User struct { - ID int64 `json:"id"` - AvatarID *int64 `json:"avatar_id"` - Passhash string `json:"passhash"` - Mail *string `json:"mail"` - Nickname string `json:"nickname"` - DispName *string `json:"disp_name"` - UserDesc *string `json:"user_desc"` - CreationDate time.Time `json:"creation_date"` - LastLogin pgtype.Timestamptz `json:"last_login"` -} - -type Usertitle struct { - UserID int64 `json:"user_id"` - TitleID int64 `json:"title_id"` - Status UsertitleStatusT `json:"status"` - Rate *int32 `json:"rate"` - ReviewText *string `json:"review_text"` - ReviewDate pgtype.Timestamptz `json:"review_date"` -} diff --git a/sql/queries.sql.go b/sql/queries.sql.go deleted file mode 100644 index c5e6f8a..0000000 --- a/sql/queries.sql.go +++ /dev/null @@ -1,370 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.30.0 -// source: queries.sql - -package sqlc - -import ( - "context" - "time" -) - -const createImage = `-- name: CreateImage :one -INSERT INTO images (storage_type, image_path) -VALUES ($1, $2) -RETURNING id, storage_type, image_path -` - -type CreateImageParams struct { - StorageType StorageTypeT `json:"storage_type"` - ImagePath string `json:"image_path"` -} - -func (q *Queries) CreateImage(ctx context.Context, arg CreateImageParams) (Image, error) { - row := q.db.QueryRow(ctx, createImage, arg.StorageType, arg.ImagePath) - var i Image - err := row.Scan(&i.ID, &i.StorageType, &i.ImagePath) - return i, err -} - -const getImageByID = `-- name: GetImageByID :one -SELECT id, storage_type, image_path -FROM images -WHERE id = $1 -` - -func (q *Queries) GetImageByID(ctx context.Context, illustID int64) (Image, error) { - row := q.db.QueryRow(ctx, getImageByID, illustID) - var i Image - err := row.Scan(&i.ID, &i.StorageType, &i.ImagePath) - return i, err -} - -const getReviewByID = `-- name: GetReviewByID :one - - -SELECT id, data, rating, illust_id, user_id, title_id, created_at -FROM reviews -WHERE review_id = $1::bigint -` - -// -- name: ListTitles :many -// SELECT title_id, title_names, studio_id, poster_id, signal_ids, -// -// title_status, rating, rating_count, release_year, release_season, -// season, episodes_aired, episodes_all, episodes_len -// -// FROM titles -// ORDER BY title_id -// LIMIT $1 OFFSET $2; -// -- name: UpdateTitle :one -// UPDATE titles -// SET -// -// title_names = COALESCE(sqlc.narg('title_names'), title_names), -// studio_id = COALESCE(sqlc.narg('studio_id'), studio_id), -// poster_id = COALESCE(sqlc.narg('poster_id'), poster_id), -// signal_ids = COALESCE(sqlc.narg('signal_ids'), signal_ids), -// title_status = COALESCE(sqlc.narg('title_status'), title_status), -// release_year = COALESCE(sqlc.narg('release_year'), release_year), -// release_season = COALESCE(sqlc.narg('release_season'), release_season), -// episodes_aired = COALESCE(sqlc.narg('episodes_aired'), episodes_aired), -// episodes_all = COALESCE(sqlc.narg('episodes_all'), episodes_all), -// episodes_len = COALESCE(sqlc.narg('episodes_len'), episodes_len) -// -// WHERE title_id = sqlc.arg('title_id') -// RETURNING *; -func (q *Queries) GetReviewByID(ctx context.Context, reviewID int64) (Review, error) { - row := q.db.QueryRow(ctx, getReviewByID, reviewID) - var i Review - err := row.Scan( - &i.ID, - &i.Data, - &i.Rating, - &i.IllustID, - &i.UserID, - &i.TitleID, - &i.CreatedAt, - ) - return i, err -} - -const getStudioByID = `-- name: GetStudioByID :one -SELECT id, studio_name, illust_id, studio_desc -FROM studios -WHERE id = $1::bigint -` - -func (q *Queries) GetStudioByID(ctx context.Context, studioID int64) (Studio, error) { - row := q.db.QueryRow(ctx, getStudioByID, studioID) - var i Studio - err := row.Scan( - &i.ID, - &i.StudioName, - &i.IllustID, - &i.StudioDesc, - ) - return i, err -} - -const getTitleByID = `-- name: GetTitleByID :one - - - - -SELECT id, title_names, studio_id, poster_id, title_status, rating, rating_count, release_year, release_season, season, episodes_aired, episodes_all, episodes_len -FROM titles -WHERE id = $1::bigint -` - -// -- name: ListUsers :many -// SELECT user_id, avatar_id, passhash, mail, nickname, disp_name, user_desc, creation_date -// FROM users -// ORDER BY user_id -// LIMIT $1 OFFSET $2; -// -- name: CreateUser :one -// INSERT INTO users (avatar_id, passhash, mail, nickname, disp_name, user_desc, creation_date) -// VALUES ($1, $2, $3, $4, $5, $6, $7) -// RETURNING user_id, avatar_id, nickname, disp_name, user_desc, creation_date; -// -- name: UpdateUser :one -// UPDATE users -// SET -// -// avatar_id = COALESCE(sqlc.narg('avatar_id'), avatar_id), -// disp_name = COALESCE(sqlc.narg('disp_name'), disp_name), -// user_desc = COALESCE(sqlc.narg('user_desc'), user_desc), -// passhash = COALESCE(sqlc.narg('passhash'), passhash) -// -// WHERE user_id = sqlc.arg('user_id') -// RETURNING user_id, avatar_id, nickname, disp_name, user_desc, creation_date; -// -- name: DeleteUser :exec -// DELETE FROM users -// WHERE user_id = $1; -func (q *Queries) GetTitleByID(ctx context.Context, titleID int64) (Title, error) { - row := q.db.QueryRow(ctx, getTitleByID, titleID) - var i Title - err := row.Scan( - &i.ID, - &i.TitleNames, - &i.StudioID, - &i.PosterID, - &i.TitleStatus, - &i.Rating, - &i.RatingCount, - &i.ReleaseYear, - &i.ReleaseSeason, - &i.Season, - &i.EpisodesAired, - &i.EpisodesAll, - &i.EpisodesLen, - ) - return i, err -} - -const getTitleTags = `-- name: GetTitleTags :many -SELECT - tag_names -FROM tags as g -JOIN title_tags as t ON(t.tag_id = g.id) -WHERE t.title_id = $1::bigint -` - -func (q *Queries) GetTitleTags(ctx context.Context, titleID int64) ([][]byte, error) { - rows, err := q.db.Query(ctx, getTitleTags, titleID) - if err != nil { - return nil, err - } - defer rows.Close() - var items [][]byte - for rows.Next() { - var tag_names []byte - if err := rows.Scan(&tag_names); err != nil { - return nil, err - } - items = append(items, tag_names) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} - -const getUserByID = `-- name: GetUserByID :one -SELECT id, avatar_id, mail, nickname, disp_name, user_desc, creation_date -FROM users -WHERE id = $1 -` - -type GetUserByIDRow struct { - ID int64 `json:"id"` - AvatarID *int64 `json:"avatar_id"` - Mail *string `json:"mail"` - Nickname string `json:"nickname"` - DispName *string `json:"disp_name"` - UserDesc *string `json:"user_desc"` - CreationDate time.Time `json:"creation_date"` -} - -func (q *Queries) GetUserByID(ctx context.Context, id int64) (GetUserByIDRow, error) { - row := q.db.QueryRow(ctx, getUserByID, id) - var i GetUserByIDRow - err := row.Scan( - &i.ID, - &i.AvatarID, - &i.Mail, - &i.Nickname, - &i.DispName, - &i.UserDesc, - &i.CreationDate, - ) - return i, err -} - -const insertStudio = `-- name: InsertStudio :one -INSERT INTO studios (studio_name, illust_id, studio_desc) -VALUES ( - $1::text, - $2::bigint, - $3::text) -RETURNING id, studio_name, illust_id, studio_desc -` - -type InsertStudioParams struct { - StudioName string `json:"studio_name"` - IllustID *int64 `json:"illust_id"` - StudioDesc *string `json:"studio_desc"` -} - -func (q *Queries) InsertStudio(ctx context.Context, arg InsertStudioParams) (Studio, error) { - row := q.db.QueryRow(ctx, insertStudio, arg.StudioName, arg.IllustID, arg.StudioDesc) - var i Studio - err := row.Scan( - &i.ID, - &i.StudioName, - &i.IllustID, - &i.StudioDesc, - ) - return i, err -} - -const insertTag = `-- name: InsertTag :one -INSERT INTO tags (tag_names) -VALUES ( - $1::jsonb) -RETURNING id, tag_names -` - -func (q *Queries) InsertTag(ctx context.Context, tagNames []byte) (Tag, error) { - row := q.db.QueryRow(ctx, insertTag, tagNames) - var i Tag - err := row.Scan(&i.ID, &i.TagNames) - return i, err -} - -const insertTitleTags = `-- name: InsertTitleTags :one -INSERT INTO title_tags (title_id, tag_id) -VALUES ( - $1::bigint, - $2::bigint) -RETURNING title_id, tag_id -` - -type InsertTitleTagsParams struct { - TitleID int64 `json:"title_id"` - TagID int64 `json:"tag_id"` -} - -func (q *Queries) InsertTitleTags(ctx context.Context, arg InsertTitleTagsParams) (TitleTag, error) { - row := q.db.QueryRow(ctx, insertTitleTags, arg.TitleID, arg.TagID) - var i TitleTag - err := row.Scan(&i.TitleID, &i.TagID) - return i, err -} - -const searchTitles = `-- name: SearchTitles :many -SELECT - id, title_names, studio_id, poster_id, title_status, rating, rating_count, release_year, release_season, season, episodes_aired, episodes_all, episodes_len -FROM titles -WHERE - CASE - WHEN $1::text IS NOT NULL THEN - ( - SELECT bool_and( - EXISTS ( - SELECT 1 - FROM jsonb_each_text(title_names) AS t(key, val) - WHERE val ILIKE pattern - ) - ) - FROM unnest( - ARRAY( - SELECT '%' || trim(w) || '%' - FROM unnest(string_to_array($1::text, ' ')) AS w - WHERE trim(w) <> '' - ) - ) AS pattern - ) - ELSE true - END - - AND ($2::title_status_t IS NULL OR title_status = $2::title_status_t) - AND ($3::float IS NULL OR rating >= $3::float) - AND ($4::int IS NULL OR release_year = $4::int) - AND ($5::release_season_t IS NULL OR release_season = $5::release_season_t) - -LIMIT COALESCE($7::int, 100) -- 100 is default limit -OFFSET $6::int -` - -type SearchTitlesParams struct { - Word *string `json:"word"` - Status *TitleStatusT `json:"status"` - Rating *float64 `json:"rating"` - ReleaseYear *int32 `json:"release_year"` - ReleaseSeason *ReleaseSeasonT `json:"release_season"` - Offset *int32 `json:"offset"` - Limit *int32 `json:"limit"` -} - -func (q *Queries) SearchTitles(ctx context.Context, arg SearchTitlesParams) ([]Title, error) { - rows, err := q.db.Query(ctx, searchTitles, - arg.Word, - arg.Status, - arg.Rating, - arg.ReleaseYear, - arg.ReleaseSeason, - arg.Offset, - arg.Limit, - ) - if err != nil { - return nil, err - } - defer rows.Close() - var items []Title - for rows.Next() { - var i Title - if err := rows.Scan( - &i.ID, - &i.TitleNames, - &i.StudioID, - &i.PosterID, - &i.TitleStatus, - &i.Rating, - &i.RatingCount, - &i.ReleaseYear, - &i.ReleaseSeason, - &i.Season, - &i.EpisodesAired, - &i.EpisodesAll, - &i.EpisodesLen, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} diff --git a/sql/sqlc.yaml b/sql/sqlc.yaml deleted file mode 100644 index f74d2ad..0000000 --- a/sql/sqlc.yaml +++ /dev/null @@ -1,37 +0,0 @@ -version: "2" -sql: - - engine: "postgresql" - queries: - - "../modules/backend/queries.sql" - schema: "migrations" - gen: - go: - package: "sqlc" - out: "." - sql_package: "pgx/v5" - sql_driver: "github.com/jackc/pgx/v5" - emit_json_tags: true - emit_pointers_for_null_types: true - overrides: - - db_type: "uuid" - nullable: false - go_type: - import: "github.com/gofrs/uuid" - package: "gofrsuuid" - type: UUID - pointer: true - - db_type: "timestamptz" - nullable: false - go_type: - import: "time" - type: "Time" - - db_type: "title_status_t" - nullable: true - go_type: - pointer: true - type: "TitleStatusT" - - db_type: "release_season_t" - nullable: true - go_type: - pointer: true - type: "ReleaseSeasonT" \ No newline at end of file