├── log.json ├── .gitignore ├── docs.go ├── .env.example ├── ui ├── public │ └── favicon.ico ├── src │ ├── vite-env.d.ts │ ├── main.js │ ├── App.svelte │ ├── lib │ │ ├── GetUserID.svelte │ │ ├── Logout.svelte │ │ └── Login.svelte │ └── stores.js ├── vite.config.js ├── package.json ├── index.html ├── jsconfig.json ├── README.md └── package-lock.json ├── redoc.html ├── models ├── category.go ├── comment.go ├── role.go ├── post.go ├── user.go └── permissions │ └── permissions.go ├── README.md ├── controllers ├── controllers.go ├── comments.go ├── categories.go ├── roles.go ├── users.go └── posts.go ├── databases ├── gorm │ ├── comments.go │ ├── roles.go │ ├── categories.go │ ├── posts.go │ ├── users.go │ └── gormdb.go └── database.go ├── Dockerfile ├── Makefile ├── tools ├── extract_user_id.go ├── db_config.go └── token.go ├── todo.md ├── .github └── workflows │ └── main.yaml ├── saggerUI.html ├── log └── log.go ├── middlewares ├── require_login.go ├── check_permissions.go └── zap_logger.go ├── docker-compose.yml ├── session └── session.go ├── main.go ├── go.mod ├── routes └── routes.go ├── docs ├── swagger.yaml ├── swagger.json └── docs.go └── go.sum /log.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .fake 3 | storage 4 | ui/node_modules 5 | ui/dist -------------------------------------------------------------------------------- /docs.go: -------------------------------------------------------------------------------- 1 | // docs.go 2 | // 3 | //go:generate swag init 4 | package main 5 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | REFRESH_TOKEN_SECRET= 2 | JWT_SECRET= 3 | PG_PASS= 4 | PG_USER= 5 | DB_NAME= -------------------------------------------------------------------------------- /ui/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arshamalh/blogo/HEAD/ui/public/favicon.ico -------------------------------------------------------------------------------- /ui/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /ui/src/main.js: -------------------------------------------------------------------------------- 1 | import App from './App.svelte' 2 | 3 | const app = new App({ 4 | target: document.getElementById('app') 5 | }) 6 | 7 | export default app 8 | -------------------------------------------------------------------------------- /ui/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import { svelte } from '@sveltejs/vite-plugin-svelte' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [svelte()] 7 | }) 8 | -------------------------------------------------------------------------------- /redoc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Redoc 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /models/category.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "gorm.io/gorm" 4 | 5 | type Category struct { 6 | gorm.Model 7 | Name string `json:"name" gorm:"uniqueIndex"` 8 | Description string `json:"description"` 9 | Posts []Post `json:"posts" gorm:"many2many:post_categories;"` 10 | } 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blogo 2 | 3 | Simple blog made using: 4 | - Go 5 | - Echo framework 6 | - Svelte frontend 7 | - Postgres Database 8 | - GORM orm 9 | - Docker virtualization 10 | - JWT 11 | - Github actions 12 | - Zap for logging 13 | 14 | In the near future 15 | - Swaggo 16 | - Dynamic Permission 17 | -------------------------------------------------------------------------------- /models/comment.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "gorm.io/gorm" 4 | 5 | type Comment struct { 6 | gorm.Model 7 | User User `json:"user"` 8 | UserID uint 9 | PostID uint `json:"post_id" from:"post_id" binding:"required"` 10 | Text string `gorm:"not null" json:"text" from:"text" binding:"required"` 11 | } 12 | -------------------------------------------------------------------------------- /controllers/controllers.go: -------------------------------------------------------------------------------- 1 | // controllers.go 2 | package controllers 3 | 4 | import ( 5 | "github.com/arshamalh/blogo/databases" 6 | "go.uber.org/zap" 7 | ) 8 | 9 | // basicAttributes defines the basic attributes shared by other controllers. 10 | type basicAttributes struct { 11 | db databases.Database 12 | logger *zap.Logger 13 | Gl *zap.Logger 14 | } 15 | -------------------------------------------------------------------------------- /ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ui", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "@sveltejs/vite-plugin-svelte": "^1.0.0-next.30", 13 | "svelte": "^3.44.0", 14 | "vite": "^2.9.9" 15 | } 16 | } -------------------------------------------------------------------------------- /models/role.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "gorm.io/gorm" 5 | ) 6 | 7 | // Any user has a role, and any role has some permissions and accessories in the future (TODO) (logo, cool name and more) 8 | type Role struct { 9 | gorm.Model 10 | Name string `gorm:"uniqueIndex" form:"name" json:"name" binding:"required"` 11 | Permissions string `form:"premissions" json:"premissions"` 12 | } 13 | -------------------------------------------------------------------------------- /ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Svelte + Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /databases/gorm/comments.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import "github.com/arshamalh/blogo/models" 4 | 5 | func (gdb *gormdb) AddComment(comment *models.Comment) error { 6 | return gdb.db.Create(comment).Error 7 | } 8 | 9 | func (gdb *gormdb) GetComment(id uint) (*models.Comment, error) { 10 | var comment models.Comment 11 | err := gdb.db.Where("id = ?", id).First(&comment).Error 12 | return &comment, err 13 | } 14 | -------------------------------------------------------------------------------- /models/post.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "gorm.io/gorm" 4 | 5 | type Post struct { 6 | gorm.Model 7 | Title string `json:"title"` 8 | Content string `json:"content"` 9 | AuthorID uint `json:"author_id"` 10 | Author User `json:"author"` 11 | Comments []Comment `json:"comments"` 12 | Categories []Category `json:"categories" gorm:"many2many:post_categories;"` 13 | } 14 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.21-alpine3.18 AS builder 2 | WORKDIR /app 3 | COPY go.mod go.sum ./ 4 | RUN go mod download 5 | COPY . ./ 6 | RUN go install github.com/swaggo/swag/cmd/swag@latest 7 | RUN swag init 8 | RUN CGO_ENABLED=0 GOOS=linux go build -a -o blogo . 9 | 10 | FROM node:18-alpine3.15 AS frontend 11 | WORKDIR /ui 12 | COPY ui . 13 | RUN npm install 14 | RUN npm run build 15 | 16 | FROM alpine:3.16 17 | COPY --from=builder /app/blogo . 18 | COPY --from=frontend /ui/dist /ui 19 | CMD ["./blogo"] 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Sample file is the default value, but shold be overriden like `make run File=data/another.json 2 | version=0.0.1 3 | name=kari 4 | 5 | help: # Generate list of targets with descriptions 6 | @grep '^.*\:\s#.*' Makefile | sed 's/\(.*\) # \(.*\)/\1 \2/' | column -t -s ":" 7 | 8 | build: # build the docker image with specified name and version 9 | docker build -t ${name}:${version} . 10 | 11 | dev: # run the application using go run command and is used for development 12 | # You should first install swag using: go install github.com/swaggo/swag/cmd/swag@latest 13 | ~/go/bin/swag init 14 | go run main.go serve 15 | -------------------------------------------------------------------------------- /tools/extract_user_id.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/labstack/echo/v4" 7 | ) 8 | 9 | // ExtractUserID extracts user ID from the request context. 10 | // returns error if there is no user ID or if the user ID is not a number. 11 | func ExtractUserID(ctx echo.Context) (uint, error) { 12 | user_id_str := ctx.Get("user_id") 13 | user_id, err := strconv.ParseUint(user_id_str.(string), 10, 64) 14 | if err != nil { 15 | return 0, err 16 | } 17 | return uint(user_id), nil 18 | } 19 | 20 | func ExtractPermissable(ctx echo.Context) bool { 21 | permissable := ctx.Get("permissable") 22 | return permissable.(bool) 23 | } 24 | -------------------------------------------------------------------------------- /todo.md: -------------------------------------------------------------------------------- 1 | - [x] Use Zap for logging 2 | - [ ] Use swaggo for better and more clear API documentation 3 | - [ ] Remove `gorm` and use `bun` instead 4 | - [ ] Use cobra to make it a cli application 5 | - [ ] Add Edit post route, including editing posts categories, id of editor should be recorded if that's not the original author. 6 | - [ ] Add comments, 7 | - [ ] Add comments related permissions such as deleting or verifying 8 | - [ ] Permissions mechanism is complex, and compression is not a good way. 9 | - [ ] Add capability to include media, hashtags and others. 10 | - [ ] Write Tests. 11 | - [ ] Remove basicAttributes from controllers 12 | - [ ] Make it a cli tool 13 | - [ ] Fix docker compose (simplify) 14 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: main 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | 7 | jobs: 8 | build-and-release: 9 | runs-on: ubuntu-20.04 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@v2 13 | 14 | - name: Login to Docker 15 | uses: docker/login-action@v1 16 | with: 17 | registry: docker.io 18 | username: ${{ secrets.DOCKER_USERNAME }} 19 | password: ${{ secrets.DOCKER_PASSWORD }} 20 | 21 | - name: Build and push Docker image 22 | uses: docker/build-push-action@v2 23 | with: 24 | context: . 25 | push: true 26 | tags: arshamalh/blogo:latest 27 | secrets: | 28 | DOCKER_BUILDKIT=1 29 | -------------------------------------------------------------------------------- /ui/src/App.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 |
9 | 10 | 11 | 12 |
13 |
14 | 15 | 35 | -------------------------------------------------------------------------------- /databases/gorm/roles.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import "github.com/arshamalh/blogo/models" 4 | 5 | func (gdb *gormdb) CreateRole(role *models.Role) error { 6 | err := gdb.db.Create(&role).Error 7 | return err 8 | } 9 | 10 | func (gdb *gormdb) UpdateRole(role *models.Role) error { 11 | err := gdb.db.Save(&role).Error 12 | return err 13 | } 14 | 15 | func (gdb *gormdb) DeleteRole(id uint) error { 16 | err := gdb.db.Delete(&models.Role{}, "id = ?", id).Error 17 | return err 18 | } 19 | 20 | func (gdb *gormdb) GetRole(id uint) (models.Role, error) { 21 | var role models.Role 22 | err := gdb.db.First(&role, id).Error 23 | return role, err 24 | } 25 | 26 | func (gdb *gormdb) GetRoles() ([]models.Role, error) { 27 | var roles []models.Role 28 | err := gdb.db.Find(&roles).Error 29 | return roles, err 30 | } 31 | -------------------------------------------------------------------------------- /databases/gorm/categories.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import "github.com/arshamalh/blogo/models" 4 | 5 | func (gdb *gormdb) CheckCategoryExists(name string) bool { 6 | results := gdb.db.Take(&models.Category{}, "name = ?", name) 7 | return results.RowsAffected > 0 8 | } 9 | 10 | func (gdb *gormdb) CreateCategory(catg *models.Category) (uint, error) { 11 | err := gdb.db.Create(&catg).Error 12 | return catg.ID, err 13 | } 14 | 15 | func (gdb *gormdb) GetCategory(name string) (*models.Category, error) { 16 | catg := &models.Category{} 17 | err := gdb.db.Preload("Post").First(&catg, "name = ?", name).Error 18 | return catg, err 19 | } 20 | 21 | func (gdb *gormdb) GetCategories() ([]models.Category, error) { 22 | var categories []models.Category 23 | err := gdb.db.Find(&categories).Error 24 | return categories, err 25 | } 26 | -------------------------------------------------------------------------------- /tools/db_config.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import "fmt" 4 | 5 | type DBConfig struct { 6 | Host string 7 | User string 8 | Password string 9 | DBName string 10 | Port int 11 | SSLMode bool 12 | TimeZone string 13 | } 14 | 15 | func (dbc DBConfig) String() string { 16 | if dbc.Host == "" { 17 | dbc.Host = "localhost" 18 | } 19 | 20 | if dbc.Port == 0 { 21 | dbc.Port = 5432 22 | } 23 | 24 | var sslmode string 25 | if dbc.SSLMode { 26 | sslmode = "enable" 27 | } else { 28 | sslmode = "disable" 29 | } 30 | 31 | if dbc.TimeZone == "" { 32 | dbc.TimeZone = "Asia/Tehran" 33 | } 34 | 35 | return fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=%s TimeZone=%s", 36 | dbc.Host, 37 | dbc.User, 38 | dbc.Password, 39 | dbc.DBName, 40 | dbc.Port, 41 | sslmode, 42 | dbc.TimeZone, 43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /saggerUI.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Swagger UI 6 | 7 | 8 | 9 |
10 | 11 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "os" 5 | 6 | "go.uber.org/zap" 7 | "go.uber.org/zap/zapcore" 8 | ) 9 | 10 | var Gl *zap.Logger 11 | 12 | func InitializeLogger() *zap.Logger { 13 | config := zap.NewProductionEncoderConfig() 14 | config.EncodeTime = zapcore.ISO8601TimeEncoder 15 | 16 | fileEncoder := zapcore.NewJSONEncoder(config) 17 | consoleEncoder := zapcore.NewConsoleEncoder(config) 18 | 19 | logFile, _ := os.OpenFile("log.json", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 20 | writer := zapcore.AddSync(logFile) 21 | defaultLogLevel := zapcore.DebugLevel 22 | core := zapcore.NewTee( 23 | zapcore.NewCore(fileEncoder, writer, defaultLogLevel), 24 | zapcore.NewCore(consoleEncoder, zapcore.AddSync(os.Stdout), defaultLogLevel), 25 | ) 26 | 27 | Gl = zap.New(core, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel)) 28 | return Gl 29 | } 30 | -------------------------------------------------------------------------------- /middlewares/require_login.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | 7 | "github.com/arshamalh/blogo/tools" 8 | "github.com/labstack/echo/v4" 9 | ) 10 | 11 | func RequireLogin(next echo.HandlerFunc) echo.HandlerFunc { 12 | return func(ctx echo.Context) error { 13 | access_token, err := ctx.Cookie("access_token") 14 | if err != nil || access_token.Value == "" { 15 | 16 | return ctx.JSON(http.StatusUnauthorized, "you should log in") 17 | } 18 | 19 | jwt_access, err := tools.ExtractTokenData(access_token.Value, os.Getenv("JWT_SECRET")) 20 | if err != nil { 21 | 22 | return ctx.JSON(http.StatusUnauthorized, "invalid token") 23 | } 24 | 25 | // If access token is valid and not expired, extract data from it 26 | userID := jwt_access.Subject 27 | ctx.Set("user_id", userID) 28 | 29 | return next(ctx) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | blogo: 4 | container_name: blogo 5 | build: . 6 | ports: 7 | - "80:80" 8 | networks: 9 | - blogo 10 | depends_on: 11 | - postgres 12 | environment: 13 | JWT_SECRET: ${JWT_SECRET} 14 | REFRESH_TOKEN_SECRET: ${REFRESH_TOKEN_SECRET} 15 | PG_PASS: ${PG_PASS} 16 | PG_USER: ${PG_USER} 17 | DB_NAME: ${DB_NAME} 18 | HOST: "postgres" 19 | postgres: 20 | container_name: postgres 21 | image: postgres:10.20-alpine3.15 22 | restart: unless-stopped 23 | ports: 24 | - 5432:5432 25 | volumes: 26 | - ./storage/pgdata:/var/lib/postgresql/data 27 | networks: 28 | - blogo 29 | environment: 30 | POSTGRES_PASSWORD: ${PG_PASS} 31 | POSTGRES_USER: ${PG_USER} 32 | POSTGRES_DB: ${DB_NAME} 33 | 34 | networks: 35 | blogo: 36 | driver: bridge 37 | -------------------------------------------------------------------------------- /databases/gorm/posts.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import "github.com/arshamalh/blogo/models" 4 | 5 | func (gdb *gormdb) CreatePost(post *models.Post) (uint, error) { 6 | err := gdb.db.Create(&post).Error 7 | return post.ID, err 8 | } 9 | 10 | func (gdb *gormdb) DeletePost(id uint) error { 11 | return gdb.db.Delete(&models.Post{}, "id = ?", id).Error 12 | } 13 | 14 | func (gdb *gormdb) UpdatePost(post *models.Post) error { 15 | return gdb.db.Updates(&post).Error 16 | } 17 | 18 | func (gdb *gormdb) GetPost(id uint) (models.Post, error) { 19 | // FIXME: This is not the best way to do this 20 | var post models.Post 21 | err := gdb.db. 22 | Preload("Author"). 23 | Preload("Category"). 24 | Preload("Comment"). 25 | First(&post, id).Error 26 | return post, err 27 | } 28 | 29 | func (gdb *gormdb) GetPosts() ([]models.Post, error) { 30 | var posts []models.Post 31 | err := gdb.db.Preload("Author").Preload("Category").Find(&posts).Error 32 | return posts, err 33 | } 34 | -------------------------------------------------------------------------------- /databases/gorm/users.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "github.com/arshamalh/blogo/models" 5 | "github.com/arshamalh/blogo/models/permissions" 6 | ) 7 | 8 | func (gdb *gormdb) CheckUserExists(username string) bool { 9 | results := gdb.db.Take(&models.User{}, "username = ?", username) 10 | return results.RowsAffected > 0 11 | } 12 | 13 | func (gdb *gormdb) CreateUser(user *models.User) (uint, error) { 14 | err := gdb.db.Create(&user).Error 15 | return user.ID, err 16 | } 17 | 18 | func (gdb *gormdb) GetUserByUsername(username string) (*models.User, error) { 19 | user := &models.User{} 20 | err := gdb.db.First(&user, "username = ?", username).Error 21 | return user, err 22 | } 23 | 24 | func (gdb *gormdb) GetUserPermissions(user_id uint) []permissions.Permission { 25 | var user models.User 26 | err := gdb.db.Preload("Role").First(&user, user_id).Error 27 | if err != nil { 28 | return nil 29 | } 30 | return permissions.Decompress(user.Role.Permissions) 31 | 32 | } 33 | -------------------------------------------------------------------------------- /ui/src/lib/GetUserID.svelte: -------------------------------------------------------------------------------- 1 | 26 | 27 | 28 |
29 | 30 | {#if msg !== ""} 31 |
{msg}
32 | {/if} 33 | {#if status !== 0} 34 |
{status}
35 | {/if} 36 |
37 | 38 | -------------------------------------------------------------------------------- /ui/src/lib/Logout.svelte: -------------------------------------------------------------------------------- 1 | 27 | 28 |
29 | 30 | {#if msg !== ""} 31 |
{msg}
32 | {/if} 33 | {#if status !== 0} 34 |
{status}
35 | {/if} 36 |
37 | 38 | -------------------------------------------------------------------------------- /models/user.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/arshamalh/blogo/log" 5 | "golang.org/x/crypto/bcrypt" 6 | "gorm.io/gorm" 7 | ) 8 | 9 | type User struct { 10 | gorm.Model 11 | Username string `json:"username" gorm:"uniqueIndex"` 12 | Password []byte `json:"-"` 13 | Email string `json:"email"` 14 | FirstName string `json:"first_name"` 15 | LastName string `json:"last_name"` 16 | Posts []Post `gorm:"foreignKey:AuthorID"` 17 | Comments []Comment `gorm:"foreignKey:UserID"` 18 | Role Role `gorm:"foreignKey:RoleID"` 19 | RoleID uint `json:"role_id"` 20 | } 21 | 22 | func (user *User) SetPassword(password string) error { 23 | hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 12) 24 | if err != nil { 25 | log.Gl.Error(err.Error()) 26 | return err 27 | } 28 | 29 | user.Password = hashedPassword 30 | return nil 31 | } 32 | 33 | func (user *User) ComparePasswords(password string) error { 34 | err := bcrypt.CompareHashAndPassword(user.Password, []byte(password)) 35 | if err != nil { 36 | log.Gl.Error(err.Error()) 37 | return err 38 | } 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /ui/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "target": "esnext", 5 | "module": "esnext", 6 | /** 7 | * svelte-preprocess cannot figure out whether you have 8 | * a value or a type, so tell TypeScript to enforce using 9 | * `import type` instead of `import` for Types. 10 | */ 11 | "importsNotUsedAsValues": "error", 12 | "isolatedModules": true, 13 | "resolveJsonModule": true, 14 | /** 15 | * To have warnings / errors of the Svelte compiler at the 16 | * correct position, enable source maps by default. 17 | */ 18 | "sourceMap": true, 19 | "esModuleInterop": true, 20 | "skipLibCheck": true, 21 | "forceConsistentCasingInFileNames": true, 22 | "baseUrl": ".", 23 | /** 24 | * Typecheck JS in `.svelte` and `.js` files by default. 25 | * Disable this if you'd like to use dynamic types. 26 | */ 27 | "checkJs": true 28 | }, 29 | /** 30 | * Use global.d.ts instead of compilerOptions.types 31 | * to avoid limiting type declarations. 32 | */ 33 | "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"] 34 | } 35 | -------------------------------------------------------------------------------- /session/session.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import "strconv" 4 | 5 | // Sessions should be stored in a database, 6 | // and it's better to use Redis for session as it's fast, 7 | // but for now, we want to keep it simple. 8 | 9 | type Session struct { 10 | SessionID string `json:"session_id"` 11 | UserID uint `json:"user_id"` 12 | Valid bool `json:"valid"` 13 | } 14 | 15 | var sessions []Session 16 | 17 | func Create(user_id uint) *Session { 18 | for _, session := range sessions { 19 | if session.UserID == user_id { 20 | return &session 21 | } 22 | } 23 | session := Session{ 24 | SessionID: strconv.Itoa(len(sessions) + 1), 25 | UserID: user_id, 26 | Valid: true, 27 | } 28 | sessions = append(sessions, session) 29 | return &session 30 | } 31 | 32 | func Get(session_id string) *Session { 33 | for _, session := range sessions { 34 | if session.SessionID == session_id { 35 | return &session 36 | } 37 | } 38 | return nil 39 | } 40 | 41 | func Invalidate(session_id string) { 42 | for i, session := range sessions { 43 | if session.SessionID == session_id { 44 | sessions[i].Valid = false 45 | return 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tools/token.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/golang-jwt/jwt" 8 | ) 9 | 10 | func GenerateToken(subject string, lifespan time.Duration, secret string) (string, error) { 11 | payload := jwt.StandardClaims{ 12 | Subject: subject, 13 | ExpiresAt: time.Now().Add(lifespan).Unix(), 14 | } 15 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, payload) 16 | return token.SignedString([]byte(secret)) 17 | } 18 | 19 | func ExtractTokenData(token string, secret string) (*jwt.StandardClaims, error) { 20 | jwt_token, err := jwt.ParseWithClaims( 21 | token, &jwt.StandardClaims{}, 22 | func(t *jwt.Token) (interface{}, error) { 23 | return []byte(secret), nil 24 | }, 25 | ) 26 | payload := jwt_token.Claims.(*jwt.StandardClaims) 27 | if err != nil { 28 | // This error usually means that the token is expired 29 | return payload, err 30 | } else if !jwt_token.Valid { 31 | // This error usually means that the token is invalid because it was not signed with the correct secret 32 | // We have access to payloads here, but we don't return it. 33 | return nil, fmt.Errorf("invalid token") 34 | } 35 | return payload, nil 36 | } 37 | -------------------------------------------------------------------------------- /databases/database.go: -------------------------------------------------------------------------------- 1 | package databases 2 | 3 | import ( 4 | "github.com/arshamalh/blogo/models" 5 | "github.com/arshamalh/blogo/models/permissions" 6 | ) 7 | 8 | type Database interface { 9 | // Users 10 | CheckUserExists(username string) bool 11 | CreateUser(user *models.User) (uint, error) 12 | GetUserByUsername(username string) (*models.User, error) 13 | GetUserPermissions(user_id uint) []permissions.Permission 14 | 15 | // Categories 16 | CheckCategoryExists(name string) bool 17 | CreateCategory(catg *models.Category) (uint, error) 18 | GetCategory(name string) (*models.Category, error) 19 | GetCategories() ([]models.Category, error) 20 | 21 | // Comments 22 | AddComment(comment *models.Comment) error 23 | GetComment(id uint) (*models.Comment, error) 24 | 25 | // Posts 26 | CreatePost(post *models.Post) (uint, error) 27 | DeletePost(id uint) error 28 | UpdatePost(post *models.Post) error 29 | GetPost(id uint) (models.Post, error) 30 | GetPosts() ([]models.Post, error) 31 | 32 | // Roles 33 | CreateRole(role *models.Role) error 34 | UpdateRole(role *models.Role) error 35 | DeleteRole(id uint) error 36 | GetRole(id uint) (models.Role, error) 37 | GetRoles() ([]models.Role, error) 38 | } 39 | -------------------------------------------------------------------------------- /ui/src/stores.js: -------------------------------------------------------------------------------- 1 | import { readable, writable } from "svelte/store"; 2 | 3 | const createWritableStore = (key, startValue) => { 4 | const { subscribe, set } = writable(startValue); 5 | 6 | return { 7 | subscribe, 8 | set, 9 | useLocalStorage: () => { 10 | const json = localStorage.getItem(key); 11 | if (json) { 12 | set(JSON.parse(json)); 13 | } 14 | 15 | subscribe((current) => { 16 | localStorage.setItem(key, JSON.stringify(current)); 17 | }); 18 | }, 19 | }; 20 | }; 21 | 22 | export const User = (function () { 23 | const { subscribe, set, useLocalStorage } = createWritableStore( 24 | "user", 25 | false 26 | ); 27 | return { 28 | subscribe, 29 | signout: () => { 30 | set(false); 31 | }, 32 | signin: (agent_name) => { 33 | set(agent_name); 34 | }, 35 | useLocalStorage, 36 | }; 37 | })(); 38 | 39 | export const ActiveAccount = (function () { 40 | const { subscribe, set, useLocalStorage } = createWritableStore( 41 | "active_account", 42 | false 43 | ); 44 | return { 45 | subscribe, 46 | set, 47 | useLocalStorage, 48 | }; 49 | })(); 50 | 51 | export const BaseURL = readable("http://localhost:80/api/v1"); 52 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | database "github.com/arshamalh/blogo/databases/gorm" 7 | "github.com/arshamalh/blogo/log" 8 | "github.com/arshamalh/blogo/routes" 9 | "github.com/arshamalh/blogo/tools" 10 | "github.com/joho/godotenv" 11 | "github.com/labstack/echo/v4" 12 | ) 13 | 14 | // @title Blogo API server 15 | // @version 1.0 16 | // @description A simple blog for educational purposes 17 | // @contact.email arshamalh.github.io/ 18 | // @host localhost:8080 19 | // @BasePath /api/v1 20 | func main() { 21 | // Load environment variables 22 | logger := log.InitializeLogger() 23 | 24 | if err := godotenv.Load(); err != nil { 25 | logger.Error(err.Error()) 26 | } 27 | 28 | // Database 29 | dsn := tools.DBConfig{ 30 | User: os.Getenv("PG_USER"), 31 | Password: os.Getenv("PG_PASS"), 32 | DBName: os.Getenv("DB_NAME"), 33 | Host: os.Getenv("HOST"), 34 | } 35 | db, err := database.Connect(dsn.String()) 36 | if err != nil { 37 | log.Gl.Error(err.Error()) 38 | return 39 | } 40 | 41 | // Router 42 | router := echo.New() 43 | routes.InitializeRoutes(router, db, logger) 44 | router.StaticFS("/", os.DirFS("./ui")) 45 | 46 | if err := router.Start(":8080"); err != nil { 47 | log.Gl.Error(err.Error()) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /middlewares/check_permissions.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "github.com/arshamalh/blogo/databases" 5 | "github.com/arshamalh/blogo/models/permissions" 6 | "github.com/arshamalh/blogo/tools" 7 | "github.com/labstack/echo/v4" 8 | ) 9 | 10 | // CheckPermissions is a middleware that checks if the user has the required permissions. 11 | func CheckPermissions(db databases.Database, permission permissions.Permission) func(next echo.HandlerFunc) echo.HandlerFunc { 12 | return func(next echo.HandlerFunc) echo.HandlerFunc { 13 | return func(ctx echo.Context) error { 14 | userID, _ := tools.ExtractUserID(ctx) 15 | hasPermission := HavePermissions(db, userID, permission) 16 | 17 | ctx.Set("permissable", hasPermission) 18 | return next(ctx) 19 | } 20 | } 21 | } 22 | 23 | // HavePermissions checks if the user has the required permissions, this can be used in other parts of the application. 24 | func HavePermissions(db databases.Database, userID uint, permission permissions.Permission) bool { 25 | for _, perm := range db.GetUserPermissions(userID) { 26 | if perm == permission || 27 | perm == permissions.FullAccess || 28 | (perm == permissions.FullContents && permission > permissions.FullContents) { 29 | return true 30 | } 31 | } 32 | return false 33 | } 34 | -------------------------------------------------------------------------------- /databases/gorm/gormdb.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/arshamalh/blogo/models" 7 | "github.com/arshamalh/blogo/models/permissions" 8 | "gorm.io/driver/postgres" 9 | "gorm.io/gorm" 10 | ) 11 | 12 | type gormdb struct { 13 | db *gorm.DB 14 | } 15 | 16 | func Connect(dsn string) (*gormdb, error) { 17 | DB, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ 18 | SkipDefaultTransaction: true, 19 | }) 20 | if err != nil { 21 | return nil, err 22 | } 23 | fmt.Println("Database connection successfully opened") 24 | 25 | // Auto migration 26 | err = DB.AutoMigrate(models.User{}, models.Post{}, models.Category{}, models.Role{}, models.Comment{}) 27 | if err != nil { 28 | return nil, err 29 | } 30 | gdb := &gormdb{db: DB} 31 | gdb.AddBasicRoles() 32 | fmt.Println("Database Migrated") 33 | return gdb, nil 34 | } 35 | 36 | // Add some basic roles manually 37 | func (gdb *gormdb) AddBasicRoles() { 38 | gdb.CreateRole(&models.Role{Name: "superadmin", Permissions: permissions.Compress([]permissions.Permission{permissions.FullAccess})}) 39 | gdb.CreateRole(&models.Role{Name: "moderator", Permissions: permissions.Compress([]permissions.Permission{permissions.FullContents})}) 40 | gdb.CreateRole(&models.Role{Name: "author", Permissions: permissions.Compress([]permissions.Permission{permissions.CreatePost, permissions.FullContents})}) 41 | } 42 | -------------------------------------------------------------------------------- /ui/src/lib/Login.svelte: -------------------------------------------------------------------------------- 1 | 29 | 30 | 31 | 42 | 43 |
44 |
45 | 46 | -------------------------------------------------------------------------------- /middlewares/zap_logger.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/labstack/echo/v4" 8 | "go.uber.org/zap" 9 | "go.uber.org/zap/zapcore" 10 | ) 11 | 12 | func ZapLogger(log *zap.Logger) echo.MiddlewareFunc { 13 | return func(next echo.HandlerFunc) echo.HandlerFunc { 14 | return func(c echo.Context) error { 15 | start := time.Now() 16 | 17 | err := next(c) 18 | if err != nil { 19 | c.Error(err) 20 | } 21 | 22 | req := c.Request() 23 | res := c.Response() 24 | 25 | fields := []zapcore.Field{ 26 | zap.String("remote_ip", c.RealIP()), 27 | zap.String("latency", time.Since(start).String()), 28 | zap.String("host", req.Host), 29 | zap.String("request", fmt.Sprintf("%s %s", req.Method, req.RequestURI)), 30 | zap.Int("status", res.Status), 31 | zap.Int64("size", res.Size), 32 | zap.String("user_agent", req.UserAgent()), 33 | } 34 | 35 | id := req.Header.Get(echo.HeaderXRequestID) 36 | if id == "" { 37 | id = res.Header().Get(echo.HeaderXRequestID) 38 | } 39 | fields = append(fields, zap.String("request_id", id)) 40 | 41 | n := res.Status 42 | switch { 43 | case n >= 500: 44 | log.With(zap.Error(err)).Error("Server error", fields...) 45 | case n >= 400: 46 | log.With(zap.Error(err)).Warn("Client error", fields...) 47 | case n >= 300: 48 | log.Info("Redirection", fields...) 49 | default: 50 | log.Info("Success", fields...) 51 | } 52 | 53 | return nil 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /models/permissions/permissions.go: -------------------------------------------------------------------------------- 1 | package permissions 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | ) 7 | 8 | // Any user has permission to delete or edit their own posts or categories, or approve comments on their own posts. 9 | // Post permissions are not related to category permissions in any way. 10 | type Permission uint8 11 | 12 | func (perm Permission) String() string { 13 | return strconv.FormatUint(uint64(perm), 10) 14 | } 15 | 16 | const ( 17 | // Full access means this user can do anything with other users, their posts, and categories. 18 | FullAccess Permission = 0 19 | 20 | CreateRole Permission = 1 21 | UpdateRole Permission = 2 22 | DeleteRole Permission = 3 23 | 24 | // Permission 4 to 20 are reserved for future use. 25 | 26 | // Full content access means this user can make or delete any post or category or comment, but can't do anything with other users. 27 | FullContents Permission = 20 28 | 29 | CreatePost Permission = 21 30 | // Users with this permission can edit others posts. 31 | EditPost Permission = 22 32 | // Users with this permission can delete others posts. 33 | DeletePost Permission = 23 34 | CreateCategory Permission = 24 35 | EditCategory Permission = 25 36 | DeleteCategory Permission = 26 37 | ApproveComment Permission = 27 38 | ) 39 | 40 | func Compress(permissions []Permission) string { 41 | var compressed string 42 | for _, permission := range permissions { 43 | compressed += permission.String() + ":" 44 | } 45 | return compressed[:len(compressed)-1] 46 | } 47 | 48 | func Decompress(compressed string) []Permission { 49 | var perms []Permission 50 | for _, perm := range strings.Split(compressed, ":") { 51 | if perm != "" { 52 | uintPerm, _ := strconv.ParseUint(perm, 10, 8) 53 | perms = append(perms, Permission(uintPerm)) 54 | } 55 | } 56 | return perms 57 | } 58 | -------------------------------------------------------------------------------- /controllers/comments.go: -------------------------------------------------------------------------------- 1 | // comments.go 2 | package controllers 3 | 4 | import ( 5 | "net/http" 6 | 7 | "github.com/arshamalh/blogo/databases" 8 | "github.com/arshamalh/blogo/log" 9 | "github.com/arshamalh/blogo/models" 10 | "github.com/arshamalh/blogo/tools" 11 | "github.com/labstack/echo/v4" 12 | "go.uber.org/zap" 13 | ) 14 | 15 | // commentController defines the controller for managing comments. 16 | type commentController struct { 17 | db databases.Database 18 | } 19 | 20 | // NewCommentController creates a new instance of commentController. 21 | func NewCommentController(db databases.Database) *commentController { 22 | return &commentController{ 23 | db: db, 24 | } 25 | } 26 | 27 | // @Summary Create a new comment 28 | // @Description Create a new comment 29 | // @ID create-comment 30 | // @Accept json 31 | // @Produce json 32 | // @Param comment body models.Comment true "Comment object" 33 | // @Success 201 {object} map[string]any 34 | // @Failure 400 {object} map[string]any 35 | // @Failure 401 {object} map[string]any 36 | // @Failure 500 {object} map[string]any 37 | // @Router /comments [post] 38 | func (cc *commentController) CreateComment(ctx echo.Context) error { 39 | var comment models.Comment 40 | userID, err := tools.ExtractUserID(ctx) 41 | if err != nil { 42 | log.Gl.Error(err.Error()) 43 | return ctx.JSON(http.StatusUnauthorized, echo.Map{"error": "there is a problem with your user"}) 44 | } 45 | 46 | if err := ctx.Bind(&comment); err != nil { 47 | log.Gl.Error(err.Error()) 48 | return ctx.JSON(http.StatusBadRequest, echo.Map{"error": "unable to parse comment"}) 49 | } 50 | 51 | comment.UserID = userID 52 | 53 | if err := cc.db.AddComment(&comment); err != nil { 54 | log.Gl.Error(err.Error()) 55 | return ctx.JSON(http.StatusInternalServerError, echo.Map{"error": "unable to add comment"}) 56 | } 57 | 58 | response := map[string]interface{}{ 59 | "comment": comment, 60 | "author_id": userID, 61 | "comment_id": comment.ID, 62 | } 63 | log.Gl.Info("Comment created", zap.Any("response", response)) 64 | return ctx.JSON(http.StatusCreated, response) 65 | } 66 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/arshamalh/blogo 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/golang-jwt/jwt v3.2.2+incompatible 7 | github.com/joho/godotenv v1.4.0 8 | github.com/swaggo/echo-swagger v1.4.1 9 | github.com/swaggo/swag v1.16.2 10 | go.uber.org/zap v1.24.0 11 | golang.org/x/crypto v0.15.0 12 | gorm.io/driver/postgres v1.3.7 13 | gorm.io/gorm v1.23.5 14 | ) 15 | 16 | require ( 17 | github.com/KyleBanks/depth v1.2.1 // indirect 18 | github.com/ghodss/yaml v1.0.0 // indirect 19 | github.com/go-openapi/jsonpointer v0.20.0 // indirect 20 | github.com/go-openapi/jsonreference v0.20.2 // indirect 21 | github.com/go-openapi/spec v0.20.9 // indirect 22 | github.com/go-openapi/swag v0.22.4 // indirect 23 | github.com/josharian/intern v1.0.0 // indirect 24 | github.com/labstack/gommon v0.4.0 // indirect 25 | github.com/mailru/easyjson v0.7.7 // indirect 26 | github.com/mattn/go-colorable v0.1.13 // indirect 27 | github.com/rogpeppe/go-internal v1.11.0 // indirect 28 | github.com/swaggo/files/v2 v2.0.0 // indirect 29 | github.com/valyala/bytebufferpool v1.0.0 // indirect 30 | github.com/valyala/fasttemplate v1.2.2 // indirect 31 | go.uber.org/atomic v1.11.0 // indirect 32 | go.uber.org/multierr v1.11.0 // indirect 33 | golang.org/x/time v0.3.0 // indirect 34 | golang.org/x/tools v0.15.0 // indirect 35 | gopkg.in/yaml.v2 v2.4.0 // indirect 36 | gopkg.in/yaml.v3 v3.0.1 // indirect 37 | ) 38 | 39 | require ( 40 | github.com/jackc/chunkreader/v2 v2.0.1 // indirect 41 | github.com/jackc/pgconn v1.12.1 // indirect 42 | github.com/jackc/pgio v1.0.0 // indirect 43 | github.com/jackc/pgpassfile v1.0.0 // indirect 44 | github.com/jackc/pgproto3/v2 v2.3.0 // indirect 45 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect 46 | github.com/jackc/pgtype v1.11.0 // indirect 47 | github.com/jackc/pgx/v4 v4.16.1 // indirect 48 | github.com/jinzhu/inflection v1.0.0 // indirect 49 | github.com/jinzhu/now v1.1.4 // indirect 50 | github.com/labstack/echo/v4 v4.10.2 51 | github.com/mattn/go-isatty v0.0.17 // indirect 52 | golang.org/x/net v0.18.0 // indirect 53 | golang.org/x/sys v0.14.0 // indirect 54 | golang.org/x/text v0.14.0 // indirect 55 | ) 56 | -------------------------------------------------------------------------------- /controllers/categories.go: -------------------------------------------------------------------------------- 1 | // category_controller.go 2 | package controllers 3 | 4 | import ( 5 | "net/http" 6 | 7 | "github.com/arshamalh/blogo/databases" 8 | "github.com/arshamalh/blogo/log" 9 | "github.com/arshamalh/blogo/models" 10 | "github.com/labstack/echo/v4" 11 | "go.uber.org/zap" 12 | ) 13 | 14 | type categoryController struct { 15 | basicAttributes 16 | } 17 | 18 | func NewCategoryController(db databases.Database, logger *zap.Logger) *categoryController { 19 | return &categoryController{ 20 | basicAttributes: basicAttributes{ 21 | db: db, 22 | logger: logger, 23 | }, 24 | } 25 | } 26 | 27 | // @Summary Create a new category 28 | // @Description Create a new category 29 | // @ID create-category 30 | // @Accept json 31 | // @Produce json 32 | // @Param category body models.Category true "Category object" 33 | // @Success 201 {object} map[string]any 34 | // @Failure 400 {object} map[string]any 35 | // @Router /categories [post] 36 | func (cc *categoryController) CreateCategory(ctx echo.Context) error { 37 | var category models.Category 38 | if err := ctx.Bind(&category); err != nil { 39 | log.Gl.Error(err.Error()) 40 | return ctx.JSON(http.StatusBadRequest, echo.Map{"status": "invalid request"}) 41 | } else if cc.db.CheckCategoryExists(category.Name) { 42 | return ctx.JSON(http.StatusConflict, echo.Map{"status": "category already exists"}) 43 | } else { 44 | _, err := cc.db.CreateCategory(&category) 45 | if err != nil { 46 | log.Gl.Error(err.Error()) 47 | return ctx.JSON(http.StatusConflict, echo.Map{"status": "cannot create category"}) 48 | } 49 | return ctx.JSON(http.StatusCreated, echo.Map{"status": "category created"}) 50 | } 51 | } 52 | 53 | // @Summary Get a category by name 54 | // @Description Get category details by name 55 | // @ID get-category 56 | // @Param name path string true "Category Name" 57 | // @Produce json 58 | // @Success 200 {object} models.Category 59 | // @Failure 404 {object} map[string]any 60 | // @Router /categories/{name} [get] 61 | func (cc *categoryController) GetCategory(ctx echo.Context) error { 62 | category, err := cc.db.GetCategory(ctx.Param("name")) 63 | if err != nil || category.ID == 0 { 64 | return ctx.JSON(http.StatusNotFound, echo.Map{"status": "category not found"}) 65 | } 66 | log.Gl.Info("Category retrieved", zap.String("category_name", category.Name)) 67 | return ctx.JSON(http.StatusOK, category) 68 | } 69 | 70 | // @Summary Get all categories 71 | // @Description Get a list of all categories 72 | // @ID get-all-categories 73 | // @Produce json 74 | // @Success 200 {array} models.Category 75 | // @Failure 404 {object} map[string]any 76 | // @Router /categories [get] 77 | func (cc *categoryController) GetCategories(ctx echo.Context) error { 78 | categories, err := cc.db.GetCategories() 79 | if err != nil { 80 | log.Gl.Error(err.Error()) 81 | return ctx.JSON(http.StatusNotFound, echo.Map{"status": "categories not found"}) 82 | } 83 | 84 | return ctx.JSON(http.StatusOK, categories) 85 | } 86 | -------------------------------------------------------------------------------- /ui/README.md: -------------------------------------------------------------------------------- 1 | # Svelte + Vite 2 | 3 | This template should help get you started developing with Svelte in Vite. 4 | 5 | ## Recommended IDE Setup 6 | 7 | [VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). 8 | 9 | ## Need an official Svelte framework? 10 | 11 | Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more. 12 | 13 | ## Technical considerations 14 | 15 | **Why use this over SvelteKit?** 16 | 17 | - It brings its own routing solution which might not be preferable for some users. 18 | - It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app. 19 | `vite dev` and `vite build` wouldn't work in a SvelteKit environment, for example. 20 | 21 | This template contains as little as possible to get started with Vite + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project. 22 | 23 | Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate. 24 | 25 | **Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?** 26 | 27 | Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information. 28 | 29 | **Why include `.vscode/extensions.json`?** 30 | 31 | Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project. 32 | 33 | **Why enable `checkJs` in the JS template?** 34 | 35 | It is likely that most cases of changing variable types in runtime are likely to be accidental, rather than deliberate. This provides advanced typechecking out of the box. Should you like to take advantage of the dynamically-typed nature of JavaScript, it is trivial to change the configuration. 36 | 37 | **Why is HMR not preserving my local component state?** 38 | 39 | HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr). 40 | 41 | If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR. 42 | 43 | ```js 44 | // store.js 45 | // An extremely simple external store 46 | import { writable } from 'svelte/store' 47 | export default writable(0) 48 | ``` 49 | -------------------------------------------------------------------------------- /routes/routes.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/arshamalh/blogo/controllers" 7 | "github.com/arshamalh/blogo/databases" 8 | "github.com/arshamalh/blogo/middlewares" 9 | "go.uber.org/zap" 10 | 11 | _ "github.com/arshamalh/blogo/docs" 12 | "github.com/arshamalh/blogo/models/permissions" 13 | "github.com/labstack/echo/v4" 14 | "github.com/labstack/echo/v4/middleware" 15 | echoSwagger "github.com/swaggo/echo-swagger" 16 | ) 17 | 18 | func InitializeRoutes(router *echo.Echo, db databases.Database, logger *zap.Logger) { 19 | // Basic configurations and middleware. 20 | router.Use(middleware.CORSWithConfig(middleware.CORSConfig{ 21 | AllowOrigins: []string{"*"}, 22 | })) 23 | 24 | router.Use(middlewares.ZapLogger(logger)) 25 | 26 | router.GET("/health", func(c echo.Context) error { 27 | return c.JSON(http.StatusNoContent, nil) 28 | }) 29 | 30 | router.GET("/docs/*", echoSwagger.WrapHandler) 31 | 32 | /// ** Routes 33 | 34 | user_routes := router.Group("/api/v1/users") 35 | uc := controllers.NewUserController(db, logger) 36 | { 37 | user_routes.POST("/register", uc.UserRegister) 38 | user_routes.POST("/login", uc.UserLogin) 39 | user_routes.POST("/logout", uc.UserLogout, middlewares.RequireLogin) 40 | user_routes.GET("/check_username", uc.CheckUsername) 41 | user_routes.GET("/id", uc.UserID, middlewares.RequireLogin) 42 | // Get & Update Profile and more 43 | // user_routes.GET("/info/:id", controllers.UserInfo) 44 | } 45 | 46 | post_routes := router.Group("api/v1/posts") 47 | pc := controllers.NewPostController(db, logger) 48 | { 49 | post_routes.POST("/", pc.CreatePost, middlewares.RequireLogin, middlewares.CheckPermissions(db, permissions.CreatePost)) 50 | post_routes.GET("/:id", pc.GetPost) 51 | post_routes.GET("/", pc.GetPosts) 52 | post_routes.PATCH("/:id", pc.UpdatePost, middlewares.RequireLogin, middlewares.CheckPermissions(db, permissions.EditPost)) 53 | post_routes.DELETE("/:id", pc.DeletePost, middlewares.RequireLogin, middlewares.CheckPermissions(db, permissions.DeletePost)) 54 | } 55 | 56 | comment_routes := router.Group("api/v1/comments") 57 | comment_controller := controllers.NewCommentController(db) 58 | { 59 | comment_routes.POST("/", comment_controller.CreateComment, middlewares.RequireLogin) 60 | } 61 | 62 | category_routes := router.Group("api/v1/categories") 63 | cc := controllers.NewCategoryController(db, logger) 64 | { 65 | category_routes.POST("/", cc.CreateCategory, middlewares.RequireLogin, middlewares.CheckPermissions(db, permissions.CreateCategory)) 66 | category_routes.GET("/:id", cc.GetCategory) 67 | category_routes.GET("/", cc.GetCategories) 68 | // category_routes.PATCH("/:id", middlewares.RequireLogin, middlewares.CheckPermissions(db, permissions.UpdateCategory), controllers.UpdateCategory) 69 | // category_routes.DELETE("/:id", middlewares.RequireLogin, middlewares.CheckPermissions(db, permissions.DeleteCategory), controllers.DeleteCategory) 70 | } 71 | 72 | role_routes := router.Group("api/v1/roles") 73 | rc := controllers.NewRoleController(db, logger) 74 | { 75 | role_routes.POST("/", rc.CreateRole, middlewares.RequireLogin, middlewares.CheckPermissions(db, permissions.CreateRole)) 76 | role_routes.PATCH("/", rc.UpdateRole, middlewares.RequireLogin, middlewares.CheckPermissions(db, permissions.UpdateRole)) 77 | role_routes.GET("/:id", rc.GetRole) 78 | role_routes.GET("/", rc.GetRoles) 79 | role_routes.DELETE("/:id", rc.DeleteRole, middlewares.RequireLogin, middlewares.CheckPermissions(db, permissions.DeleteRole)) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /docs/swagger.yaml: -------------------------------------------------------------------------------- 1 | basePath: /api/v1 2 | definitions: 3 | models.Category: 4 | type: object 5 | models.Comment: 6 | type: object 7 | host: localhost:8080 8 | info: 9 | contact: 10 | email: arshamalh.github.io/ 11 | description: A simple blog for educational purposes 12 | title: Blogo API server 13 | version: "1.0" 14 | paths: 15 | /categories: 16 | get: 17 | description: Get a list of all categories 18 | operationId: get-all-categories 19 | produces: 20 | - application/json 21 | responses: 22 | "200": 23 | description: OK 24 | schema: 25 | items: 26 | $ref: '#/definitions/models.Category' 27 | type: array 28 | "404": 29 | description: Not Found 30 | schema: 31 | additionalProperties: true 32 | type: object 33 | summary: Get all categories 34 | post: 35 | consumes: 36 | - application/json 37 | description: Create a new category 38 | operationId: create-category 39 | parameters: 40 | - description: Category object 41 | in: body 42 | name: category 43 | required: true 44 | schema: 45 | $ref: '#/definitions/models.Category' 46 | produces: 47 | - application/json 48 | responses: 49 | "201": 50 | description: Created 51 | schema: 52 | additionalProperties: true 53 | type: object 54 | "400": 55 | description: Bad Request 56 | schema: 57 | additionalProperties: true 58 | type: object 59 | summary: Create a new category 60 | /categories/{name}: 61 | get: 62 | description: Get category details by name 63 | operationId: get-category 64 | parameters: 65 | - description: Category Name 66 | in: path 67 | name: name 68 | required: true 69 | type: string 70 | produces: 71 | - application/json 72 | responses: 73 | "200": 74 | description: OK 75 | schema: 76 | $ref: '#/definitions/models.Category' 77 | "404": 78 | description: Not Found 79 | schema: 80 | additionalProperties: true 81 | type: object 82 | summary: Get a category by name 83 | /comments: 84 | post: 85 | consumes: 86 | - application/json 87 | description: Create a new comment 88 | operationId: create-comment 89 | parameters: 90 | - description: Comment object 91 | in: body 92 | name: comment 93 | required: true 94 | schema: 95 | $ref: '#/definitions/models.Comment' 96 | produces: 97 | - application/json 98 | responses: 99 | "201": 100 | description: Created 101 | schema: 102 | additionalProperties: true 103 | type: object 104 | "400": 105 | description: Bad Request 106 | schema: 107 | additionalProperties: true 108 | type: object 109 | "401": 110 | description: Unauthorized 111 | schema: 112 | additionalProperties: true 113 | type: object 114 | "500": 115 | description: Internal Server Error 116 | schema: 117 | additionalProperties: true 118 | type: object 119 | summary: Create a new comment 120 | /users/register: 121 | post: 122 | consumes: 123 | - application/json 124 | description: Register a user 125 | parameters: 126 | - description: Username 127 | in: body 128 | name: Username 129 | required: true 130 | schema: 131 | type: string 132 | - description: Password 133 | in: body 134 | name: Password 135 | required: true 136 | schema: 137 | type: string 138 | - description: Email 139 | in: body 140 | name: Email 141 | required: true 142 | schema: 143 | type: string 144 | - description: FirstName 145 | in: body 146 | name: FirstName 147 | schema: 148 | type: string 149 | - description: LastName 150 | in: body 151 | name: LastName 152 | schema: 153 | type: string 154 | produces: 155 | - application/json 156 | responses: 157 | "201": 158 | description: Created 159 | schema: 160 | additionalProperties: true 161 | type: object 162 | "400": 163 | description: Bad Request 164 | schema: 165 | additionalProperties: true 166 | type: object 167 | "409": 168 | description: Conflict 169 | schema: 170 | additionalProperties: true 171 | type: object 172 | summary: Register a user 173 | swagger: "2.0" 174 | -------------------------------------------------------------------------------- /controllers/roles.go: -------------------------------------------------------------------------------- 1 | // roles.go 2 | package controllers 3 | 4 | import ( 5 | "net/http" 6 | "strconv" 7 | 8 | "github.com/arshamalh/blogo/databases" 9 | "github.com/arshamalh/blogo/log" 10 | "github.com/arshamalh/blogo/models" 11 | "github.com/labstack/echo/v4" 12 | "go.uber.org/zap" 13 | ) 14 | 15 | // roleController handles HTTP requests related to user roles. 16 | type roleController struct { 17 | basicAttributes 18 | } 19 | 20 | // NewRoleController creates a new instance of roleController. 21 | func NewRoleController(db databases.Database, logger *zap.Logger) *roleController { 22 | return &roleController{ 23 | basicAttributes: basicAttributes{ 24 | db: db, 25 | logger: logger, 26 | }, 27 | } 28 | } 29 | 30 | // CreateRole creates a new user role. 31 | // swagger:route POST /roles CreateRole 32 | // Creates a new user role. 33 | // responses: 34 | // 35 | // 201: map[string]interface{} "Role created" 36 | // 400: map[string]interface{} "Invalid request" 37 | // 500: map[string]interface{} "Internal server error" 38 | func (rc *roleController) CreateRole(ctx echo.Context) error { 39 | var role models.Role 40 | if err := ctx.Bind(&role); err != nil { 41 | log.Gl.Error(err.Error()) 42 | return ctx.JSON(http.StatusBadRequest, echo.Map{"status": "invalid request"}) 43 | } 44 | if err := rc.db.CreateRole(&role); err != nil { 45 | log.Gl.Error(err.Error()) 46 | return ctx.JSON(http.StatusInternalServerError, echo.Map{"error": err.Error()}) 47 | } 48 | log.Gl.Info("Role created", zap.String("role_name", role.Name)) 49 | return ctx.JSON(http.StatusCreated, echo.Map{"role": role}) 50 | } 51 | 52 | // UpdateRole updates an existing user role. 53 | // swagger:route PUT /roles UpdateRole 54 | // Updates an existing user role. 55 | // responses: 56 | // 57 | // 200: map[string]interface{} "Role updated" 58 | // 400: map[string]interface{} "Invalid request" 59 | // 500: map[string]interface{} "Internal server error" 60 | func (rc *roleController) UpdateRole(ctx echo.Context) error { 61 | var role models.Role 62 | if err := ctx.Bind(&role); err != nil { 63 | log.Gl.Error(err.Error()) 64 | return ctx.JSON(http.StatusBadRequest, echo.Map{"status": "invalid request"}) 65 | } 66 | if err := rc.db.UpdateRole(&role); err != nil { 67 | log.Gl.Error(err.Error()) 68 | return ctx.JSON(http.StatusInternalServerError, echo.Map{"error": err.Error()}) 69 | } 70 | log.Gl.Info("Role updated", zap.String("role_name", role.Name)) 71 | return ctx.JSON(http.StatusOK, echo.Map{"role": role}) 72 | } 73 | 74 | // DeleteRole deletes an existing user role. 75 | // swagger:route DELETE /roles/{id} DeleteRole 76 | // Deletes an existing user role. 77 | // parameters: 78 | // - name: id 79 | // in: path 80 | // description: The ID of the role to delete. 81 | // required: true 82 | // type: integer 83 | // 84 | // responses: 85 | // 86 | // 200: map[string]interface{} "Role deleted" 87 | // 500: map[string]interface{} "Internal server error" 88 | func (rc *roleController) DeleteRole(ctx echo.Context) error { 89 | id, _ := strconv.ParseUint(ctx.Param("id"), 10, 64) 90 | if err := rc.db.DeleteRole(uint(id)); err != nil { 91 | log.Gl.Error(err.Error()) 92 | return ctx.JSON(http.StatusInternalServerError, echo.Map{"error": err.Error()}) 93 | } 94 | log.Gl.Info("Role deleted", zap.Uint64("role_id", id)) 95 | return ctx.JSON(http.StatusOK, echo.Map{"message": "Role deleted"}) 96 | } 97 | 98 | // GetRole retrieves a user role by ID. 99 | // swagger:route GET /roles/{id} GetRole 100 | // Retrieves a user role by ID. 101 | // parameters: 102 | // - name: id 103 | // in: path 104 | // description: The ID of the role to retrieve. 105 | // required: true 106 | // type: integer 107 | // 108 | // responses: 109 | // 110 | // 200: map[string]interface{} "Role retrieved" 111 | // 404: map[string]interface{} "Role not found" 112 | // 500: map[string]interface{} "Internal server error" 113 | func (rc *roleController) GetRole(ctx echo.Context) error { 114 | id, _ := strconv.ParseUint(ctx.Param("id"), 10, 64) 115 | role, err := rc.db.GetRole(uint(id)) 116 | if err != nil { 117 | log.Gl.Error(err.Error()) 118 | return ctx.JSON(http.StatusInternalServerError, echo.Map{"error": err.Error()}) 119 | } 120 | log.Gl.Info("Role retrieved", zap.Uint64("role_id", id)) 121 | return ctx.JSON(http.StatusOK, echo.Map{"role": role}) 122 | } 123 | 124 | // GetRoles retrieves all user roles. 125 | // swagger:route GET /roles GetRoles 126 | // Retrieves all user roles. 127 | // responses: 128 | // 129 | // 200: map[string]interface{} "Roles retrieved" 130 | // 500: map[string]interface{} "Internal server error" 131 | func (rc *roleController) GetRoles(ctx echo.Context) error { 132 | roles, err := rc.db.GetRoles() 133 | if err != nil { 134 | log.Gl.Error(err.Error()) 135 | return ctx.JSON(http.StatusInternalServerError, echo.Map{"error": err.Error()}) 136 | } 137 | log.Gl.Info("Roles retrieved", zap.Int("total_roles", len(roles))) 138 | return ctx.JSON(http.StatusOK, echo.Map{"roles": roles}) 139 | } 140 | -------------------------------------------------------------------------------- /controllers/users.go: -------------------------------------------------------------------------------- 1 | // user.go 2 | package controllers 3 | 4 | import ( 5 | "net/http" 6 | "time" 7 | 8 | "github.com/arshamalh/blogo/databases" 9 | "github.com/arshamalh/blogo/log" 10 | "github.com/arshamalh/blogo/models" 11 | "github.com/arshamalh/blogo/session" 12 | "github.com/labstack/echo/v4" 13 | "go.uber.org/zap" 14 | ) 15 | 16 | // userController handles HTTP requests related to user management 17 | type userController struct { 18 | basicAttributes 19 | } 20 | 21 | // NewUserController creates a new instance of userController. 22 | func NewUserController(db databases.Database, logger *zap.Logger) *userController { 23 | return &userController{ 24 | basicAttributes: basicAttributes{ 25 | db: db, 26 | logger: logger, 27 | }, 28 | } 29 | } 30 | 31 | // UserRegisterRequest represents the request format for user registration. 32 | // swagger:parameters UserRegister 33 | type UserRegisterRequest struct { 34 | Username string `form:"username" json:"username" binding:"required"` 35 | Password string `form:"password" json:"password" binding:"required"` 36 | Email string `form:"email" json:"email" binding:"required"` 37 | FirstName string `form:"first_name" json:"first_name"` 38 | LastName string `form:"last_name" json:"last_name"` 39 | } 40 | 41 | // UserLoginRequest represents the request format for user login. 42 | // swagger:parameters UserLogin 43 | type UserLoginRequest struct { 44 | Username string `form:"username" json:"username" binding:"required"` 45 | Password string `form:"password" json:"password" binding:"required"` 46 | } 47 | 48 | // ShowAccount godoc 49 | // @Summary Register a user 50 | // @Description Register a user 51 | // @Accept json 52 | // @Produce json 53 | // @Param Username body string true "Username" 54 | // @Param Password body string true "Password" 55 | // @Param Email body string true "Email" 56 | // @Param FirstName body string false "FirstName" 57 | // @Param LastName body string false "LastName" 58 | // @Success 201 {object} map[string]any 59 | // @Failure 400 {object} map[string]any 60 | // @Failure 409 {object} map[string]any 61 | // @Router /users/register [post] 62 | func (uc *userController) UserRegister(ctx echo.Context) error { 63 | var user UserRegisterRequest 64 | if ctx.Bind(&user) != nil { 65 | return ctx.JSON(http.StatusBadRequest, echo.Map{"message": "invalid request"}) 66 | } 67 | if uc.db.CheckUserExists(user.Username) { 68 | return ctx.JSON(http.StatusConflict, echo.Map{"message": "user already exists"}) 69 | } 70 | new_user := models.User{ 71 | Username: user.Username, 72 | Email: user.Email, 73 | FirstName: user.FirstName, 74 | LastName: user.LastName, 75 | RoleID: 3, 76 | } 77 | if err := new_user.SetPassword(user.Password); err != nil { 78 | return ctx.JSON(http.StatusInternalServerError, echo.Map{"message": "cannot register you"}) 79 | } 80 | uid, err := uc.db.CreateUser(&new_user) 81 | if err != nil { 82 | log.Gl.Error(err.Error()) 83 | return ctx.JSON(http.StatusConflict, echo.Map{"message": "Failed to create user"}) 84 | } 85 | log.Gl.Info("User created", zap.String("username", new_user.Username)) 86 | return ctx.JSON(http.StatusCreated, echo.Map{"message": "user created", "uid": uid}) 87 | } 88 | 89 | // CheckUsername checks the availability of a username. 90 | // swagger:route GET /users/check-username CheckUsername 91 | // Checks the availability of a username. 92 | // responses: 93 | // 94 | // 200: map[string]interface{} "Username available" 95 | // 400: map[string]interface{} "Invalid request" 96 | // 409: map[string]interface{} "Username already taken" 97 | func (uc *userController) UserLogin(ctx echo.Context) error { 98 | // Decode the body of request 99 | var user UserLoginRequest 100 | if ctx.Bind(&user) != nil { 101 | return ctx.JSON(http.StatusBadRequest, echo.Map{"message": "invalid request"}) 102 | } 103 | 104 | // Check if user exists 105 | dbUser, err := uc.db.GetUserByUsername(user.Username) 106 | if err != nil { 107 | log.Gl.Error(err.Error()) 108 | return ctx.JSON(http.StatusInternalServerError, echo.Map{"message": "error getting user"}) 109 | } 110 | 111 | // Check if password is correct 112 | if dbUser.ComparePasswords(user.Password) != nil { 113 | return ctx.JSON(http.StatusUnauthorized, echo.Map{"message": "wrong password"}) 114 | } 115 | 116 | // Store username in the session 117 | sn := session.Create(dbUser.ID) 118 | 119 | // Generate access token and refresh token 120 | return ctx.JSON(http.StatusOK, echo.Map{"message": "login success", "session": sn}) 121 | } 122 | 123 | func (uc *userController) CheckUsername(ctx echo.Context) error { 124 | var username string 125 | if ctx.Bind(&username) != nil { 126 | return ctx.JSON(http.StatusBadRequest, echo.Map{"message": "invalid request"}) 127 | } else if uc.db.CheckUserExists(username) { 128 | return ctx.JSON(http.StatusConflict, echo.Map{"message": "username is already taken"}) 129 | } else { 130 | return ctx.JSON(http.StatusOK, echo.Map{"message": "username available"}) 131 | } 132 | } 133 | 134 | // UserLogout logs out a user. 135 | // swagger:route POST /users/logout UserLogout 136 | // Logs out a user. 137 | // responses: 138 | // 139 | // 200: map[string]interface{} "Logout success" 140 | func (uc *userController) UserLogout(ctx echo.Context) error { 141 | ctx.SetCookie(&http.Cookie{ 142 | Name: "access_token", 143 | Path: "/", 144 | Expires: time.Now(), 145 | }) 146 | return ctx.JSON(http.StatusOK, echo.Map{"message": "logout success"}) 147 | } 148 | 149 | // UserID retrieves the ID of the logged-in user. 150 | // swagger:route GET /users/user-id UserID 151 | // Retrieves the ID of the logged-in user. 152 | // responses: 153 | // 154 | // 200: map[string]interface{} "User ID retrieved" 155 | func (uc *userController) UserID(ctx echo.Context) error { 156 | return ctx.JSON(http.StatusOK, echo.Map{"user_id": ctx.Get("user_id")}) 157 | } 158 | -------------------------------------------------------------------------------- /controllers/posts.go: -------------------------------------------------------------------------------- 1 | // posts.go 2 | package controllers 3 | 4 | import ( 5 | "net/http" 6 | "strconv" 7 | 8 | "github.com/arshamalh/blogo/databases" 9 | "github.com/arshamalh/blogo/log" 10 | "github.com/arshamalh/blogo/models" 11 | "github.com/arshamalh/blogo/tools" 12 | "github.com/labstack/echo/v4" 13 | "go.uber.org/zap" 14 | ) 15 | 16 | // PostRequest represents the request structure for creating a post. 17 | // swagger:parameters CreatePost 18 | type PostRequest struct { 19 | Title string `form:"title" json:"title" binding:"required"` 20 | Content string `form:"content" json:"content" binding:"required"` 21 | Categories []string `form:"categories" json:"categories"` 22 | } 23 | 24 | // postController handles HTTP requests related to blog posts. 25 | type postController struct { 26 | basicAttributes 27 | } 28 | 29 | // NewPostController creates a new instance of postController. 30 | func NewPostController(db databases.Database, logger *zap.Logger) *postController { 31 | return &postController{ 32 | basicAttributes: basicAttributes{ 33 | db: db, 34 | logger: logger, 35 | }, 36 | } 37 | } 38 | 39 | // CreatePost creates a new blog post. 40 | // swagger:route POST /posts CreatePost 41 | // Creates a new blog post. 42 | // responses: 43 | // 44 | // 201: map[string]interface{} "Post created successfully" 45 | // 400: map[string]interface{} "Invalid request" 46 | // 401: map[string]interface{} "Unauthorized" 47 | // 409: map[string]interface{} "Cannot make the post" 48 | // 500: map[string]interface{} "Internal server error" 49 | func (pc *postController) CreatePost(ctx echo.Context) error { 50 | var post PostRequest 51 | userID, _ := tools.ExtractUserID(ctx) 52 | if err := ctx.Bind(&post); err != nil { 53 | log.Gl.Error(err.Error()) 54 | return ctx.JSON(500, echo.Map{"error": err.Error()}) 55 | } 56 | 57 | categories := []models.Category{} 58 | for _, category := range post.Categories { 59 | categories = append(categories, models.Category{Name: category}) 60 | } 61 | 62 | newPost := models.Post{ 63 | Title: post.Title, 64 | Content: post.Content, 65 | AuthorID: userID, 66 | Categories: categories, 67 | } 68 | postID, err := pc.db.CreatePost(&newPost) 69 | if err != nil { 70 | log.Gl.Error(err.Error()) 71 | return ctx.JSON(http.StatusConflict, echo.Map{"message": "cannot make the post"}) 72 | } 73 | 74 | log.Gl.Info("Post created", zap.Any("post_id", postID)) 75 | return ctx.JSON(http.StatusCreated, echo.Map{ 76 | "message": "Post created successfully", 77 | "post_id": postID, 78 | }) 79 | } 80 | 81 | // DeletePost deletes a blog post. 82 | // swagger:route DELETE /posts/{id} DeletePost 83 | // Deletes a blog post. 84 | // parameters: 85 | // - name: id 86 | // in: path 87 | // description: The ID of the post to delete. 88 | // required: true 89 | // type: integer 90 | // 91 | // responses: 92 | // 93 | // 200: map[string]interface{} "Post deleted" 94 | // 401: map[string]interface{} "Unauthorized" 95 | // 500: map[string]interface{} "Internal server error" 96 | func (pc *postController) DeletePost(ctx echo.Context) error { 97 | userID, _ := tools.ExtractUserID(ctx) 98 | postID, _ := strconv.ParseUint(ctx.Param("id"), 10, 64) 99 | 100 | // If the user doesn't have the permission to delete posts, check if it's their own post. 101 | if !tools.ExtractPermissable(ctx) { 102 | post, _ := pc.db.GetPost(uint(postID)) 103 | if post.AuthorID != userID { 104 | return ctx.JSON(http.StatusUnauthorized, echo.Map{ 105 | "message": "you don't have enough permissions", 106 | }) 107 | } 108 | } 109 | 110 | if err := pc.db.DeletePost(uint(postID)); err != nil { 111 | log.Gl.Error(err.Error()) 112 | return ctx.JSON(http.StatusInternalServerError, echo.Map{"error": err.Error()}) 113 | } 114 | log.Gl.Info("Post deleted", zap.Uint("post_id", uint(postID))) 115 | return ctx.JSON(http.StatusOK, echo.Map{"message": "Post deleted"}) 116 | } 117 | 118 | // UpdatePost updates a blog post. 119 | // swagger:route PUT /posts/{id} UpdatePost 120 | // Updates a blog post. 121 | // parameters: 122 | // - name: id 123 | // in: path 124 | // description: The ID of the post to update. 125 | // required: true 126 | // type: integer 127 | // 128 | // responses: 129 | // 130 | // 200: map[string]interface{} "Post updated" 131 | // 400: map[string]interface{} "Invalid request" 132 | // 500: map[string]interface{} "Internal server error" 133 | func (pc *postController) UpdatePost(ctx echo.Context) error { 134 | var post PostRequest 135 | if err := ctx.Bind(&post); err != nil { 136 | log.Gl.Error(err.Error()) 137 | return ctx.JSON(http.StatusBadRequest, echo.Map{"status": "invalid request"}) 138 | } 139 | 140 | // Make a new updated post 141 | categories := []models.Category{} 142 | for _, category := range post.Categories { 143 | categories = append(categories, models.Category{Name: category}) 144 | } 145 | newPost := models.Post{ 146 | Title: post.Title, 147 | Content: post.Content, 148 | Categories: categories, 149 | } 150 | postID, _ := strconv.ParseUint(ctx.Param("id"), 10, 64) 151 | newPost.ID = uint(postID) 152 | 153 | // Update post 154 | if err := pc.db.UpdatePost(&newPost); err != nil { 155 | log.Gl.Error(err.Error()) 156 | return ctx.JSON(500, echo.Map{"error": err.Error()}) 157 | } 158 | log.Gl.Info("Post updated", zap.Uint("post_id", uint(postID))) 159 | return ctx.JSON(200, echo.Map{"post": newPost}) 160 | } 161 | 162 | // GetPost retrieves a blog post by ID. 163 | // swagger:route GET /posts/{id} GetPost 164 | // Retrieves a blog post by ID. 165 | // parameters: 166 | // - name: id 167 | // in: path 168 | // description: The ID of the post to retrieve. 169 | // required: true 170 | // type: integer 171 | // 172 | // responses: 173 | // 174 | // 200: map[string]interface{} "Post retrieved" 175 | // 404: map[string]interface{} "Post not found" 176 | // 500: map[string]interface{} "Internal server error" 177 | func (pc *postController) GetPost(ctx echo.Context) error { 178 | postID, _ := strconv.ParseUint(ctx.Param("id"), 10, 64) 179 | post, _ := pc.db.GetPost(uint(postID)) 180 | log.Gl.Info("Post retrieved", zap.Uint("post_id", uint(postID))) 181 | return ctx.JSON(200, echo.Map{"post": post}) 182 | } 183 | 184 | // GetPosts retrieves all blog posts. 185 | // swagger:route GET /posts GetPosts 186 | // Retrieves all blog posts. 187 | // responses: 188 | // 189 | // 200: map[string]interface{} "Posts retrieved" 190 | // 404: map[string]interface{} "Posts not found" 191 | // 500: map[string]interface{} "Internal server error" 192 | func (pc *postController) GetPosts(ctx echo.Context) error { 193 | posts, _ := pc.db.GetPosts() 194 | return ctx.JSON(200, echo.Map{"posts": posts}) 195 | } 196 | -------------------------------------------------------------------------------- /docs/swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "description": "A simple blog for educational purposes", 5 | "title": "Blogo API server", 6 | "contact": { 7 | "email": "arshamalh.github.io/" 8 | }, 9 | "version": "1.0" 10 | }, 11 | "host": "localhost:8080", 12 | "basePath": "/api/v1", 13 | "paths": { 14 | "/categories": { 15 | "get": { 16 | "description": "Get a list of all categories", 17 | "produces": [ 18 | "application/json" 19 | ], 20 | "summary": "Get all categories", 21 | "operationId": "get-all-categories", 22 | "responses": { 23 | "200": { 24 | "description": "OK", 25 | "schema": { 26 | "type": "array", 27 | "items": { 28 | "$ref": "#/definitions/models.Category" 29 | } 30 | } 31 | }, 32 | "404": { 33 | "description": "Not Found", 34 | "schema": { 35 | "type": "object", 36 | "additionalProperties": true 37 | } 38 | } 39 | } 40 | }, 41 | "post": { 42 | "description": "Create a new category", 43 | "consumes": [ 44 | "application/json" 45 | ], 46 | "produces": [ 47 | "application/json" 48 | ], 49 | "summary": "Create a new category", 50 | "operationId": "create-category", 51 | "parameters": [ 52 | { 53 | "description": "Category object", 54 | "name": "category", 55 | "in": "body", 56 | "required": true, 57 | "schema": { 58 | "$ref": "#/definitions/models.Category" 59 | } 60 | } 61 | ], 62 | "responses": { 63 | "201": { 64 | "description": "Created", 65 | "schema": { 66 | "type": "object", 67 | "additionalProperties": true 68 | } 69 | }, 70 | "400": { 71 | "description": "Bad Request", 72 | "schema": { 73 | "type": "object", 74 | "additionalProperties": true 75 | } 76 | } 77 | } 78 | } 79 | }, 80 | "/categories/{name}": { 81 | "get": { 82 | "description": "Get category details by name", 83 | "produces": [ 84 | "application/json" 85 | ], 86 | "summary": "Get a category by name", 87 | "operationId": "get-category", 88 | "parameters": [ 89 | { 90 | "type": "string", 91 | "description": "Category Name", 92 | "name": "name", 93 | "in": "path", 94 | "required": true 95 | } 96 | ], 97 | "responses": { 98 | "200": { 99 | "description": "OK", 100 | "schema": { 101 | "$ref": "#/definitions/models.Category" 102 | } 103 | }, 104 | "404": { 105 | "description": "Not Found", 106 | "schema": { 107 | "type": "object", 108 | "additionalProperties": true 109 | } 110 | } 111 | } 112 | } 113 | }, 114 | "/comments": { 115 | "post": { 116 | "description": "Create a new comment", 117 | "consumes": [ 118 | "application/json" 119 | ], 120 | "produces": [ 121 | "application/json" 122 | ], 123 | "summary": "Create a new comment", 124 | "operationId": "create-comment", 125 | "parameters": [ 126 | { 127 | "description": "Comment object", 128 | "name": "comment", 129 | "in": "body", 130 | "required": true, 131 | "schema": { 132 | "$ref": "#/definitions/models.Comment" 133 | } 134 | } 135 | ], 136 | "responses": { 137 | "201": { 138 | "description": "Created", 139 | "schema": { 140 | "type": "object", 141 | "additionalProperties": true 142 | } 143 | }, 144 | "400": { 145 | "description": "Bad Request", 146 | "schema": { 147 | "type": "object", 148 | "additionalProperties": true 149 | } 150 | }, 151 | "401": { 152 | "description": "Unauthorized", 153 | "schema": { 154 | "type": "object", 155 | "additionalProperties": true 156 | } 157 | }, 158 | "500": { 159 | "description": "Internal Server Error", 160 | "schema": { 161 | "type": "object", 162 | "additionalProperties": true 163 | } 164 | } 165 | } 166 | } 167 | }, 168 | "/users/register": { 169 | "post": { 170 | "description": "Register a user", 171 | "consumes": [ 172 | "application/json" 173 | ], 174 | "produces": [ 175 | "application/json" 176 | ], 177 | "summary": "Register a user", 178 | "parameters": [ 179 | { 180 | "description": "Username", 181 | "name": "Username", 182 | "in": "body", 183 | "required": true, 184 | "schema": { 185 | "type": "string" 186 | } 187 | }, 188 | { 189 | "description": "Password", 190 | "name": "Password", 191 | "in": "body", 192 | "required": true, 193 | "schema": { 194 | "type": "string" 195 | } 196 | }, 197 | { 198 | "description": "Email", 199 | "name": "Email", 200 | "in": "body", 201 | "required": true, 202 | "schema": { 203 | "type": "string" 204 | } 205 | }, 206 | { 207 | "description": "FirstName", 208 | "name": "FirstName", 209 | "in": "body", 210 | "schema": { 211 | "type": "string" 212 | } 213 | }, 214 | { 215 | "description": "LastName", 216 | "name": "LastName", 217 | "in": "body", 218 | "schema": { 219 | "type": "string" 220 | } 221 | } 222 | ], 223 | "responses": { 224 | "201": { 225 | "description": "Created", 226 | "schema": { 227 | "type": "object", 228 | "additionalProperties": true 229 | } 230 | }, 231 | "400": { 232 | "description": "Bad Request", 233 | "schema": { 234 | "type": "object", 235 | "additionalProperties": true 236 | } 237 | }, 238 | "409": { 239 | "description": "Conflict", 240 | "schema": { 241 | "type": "object", 242 | "additionalProperties": true 243 | } 244 | } 245 | } 246 | } 247 | } 248 | }, 249 | "definitions": { 250 | "models.Category": { 251 | "type": "object" 252 | }, 253 | "models.Comment": { 254 | "type": "object" 255 | } 256 | } 257 | } -------------------------------------------------------------------------------- /docs/docs.go: -------------------------------------------------------------------------------- 1 | // Package docs Code generated by swaggo/swag. DO NOT EDIT 2 | package docs 3 | 4 | import "github.com/swaggo/swag" 5 | 6 | const docTemplate = `{ 7 | "schemes": {{ marshal .Schemes }}, 8 | "swagger": "2.0", 9 | "info": { 10 | "description": "{{escape .Description}}", 11 | "title": "{{.Title}}", 12 | "contact": { 13 | "email": "arshamalh.github.io/" 14 | }, 15 | "version": "{{.Version}}" 16 | }, 17 | "host": "{{.Host}}", 18 | "basePath": "{{.BasePath}}", 19 | "paths": { 20 | "/categories": { 21 | "get": { 22 | "description": "Get a list of all categories", 23 | "produces": [ 24 | "application/json" 25 | ], 26 | "summary": "Get all categories", 27 | "operationId": "get-all-categories", 28 | "responses": { 29 | "200": { 30 | "description": "OK", 31 | "schema": { 32 | "type": "array", 33 | "items": { 34 | "$ref": "#/definitions/models.Category" 35 | } 36 | } 37 | }, 38 | "404": { 39 | "description": "Not Found", 40 | "schema": { 41 | "type": "object", 42 | "additionalProperties": true 43 | } 44 | } 45 | } 46 | }, 47 | "post": { 48 | "description": "Create a new category", 49 | "consumes": [ 50 | "application/json" 51 | ], 52 | "produces": [ 53 | "application/json" 54 | ], 55 | "summary": "Create a new category", 56 | "operationId": "create-category", 57 | "parameters": [ 58 | { 59 | "description": "Category object", 60 | "name": "category", 61 | "in": "body", 62 | "required": true, 63 | "schema": { 64 | "$ref": "#/definitions/models.Category" 65 | } 66 | } 67 | ], 68 | "responses": { 69 | "201": { 70 | "description": "Created", 71 | "schema": { 72 | "type": "object", 73 | "additionalProperties": true 74 | } 75 | }, 76 | "400": { 77 | "description": "Bad Request", 78 | "schema": { 79 | "type": "object", 80 | "additionalProperties": true 81 | } 82 | } 83 | } 84 | } 85 | }, 86 | "/categories/{name}": { 87 | "get": { 88 | "description": "Get category details by name", 89 | "produces": [ 90 | "application/json" 91 | ], 92 | "summary": "Get a category by name", 93 | "operationId": "get-category", 94 | "parameters": [ 95 | { 96 | "type": "string", 97 | "description": "Category Name", 98 | "name": "name", 99 | "in": "path", 100 | "required": true 101 | } 102 | ], 103 | "responses": { 104 | "200": { 105 | "description": "OK", 106 | "schema": { 107 | "$ref": "#/definitions/models.Category" 108 | } 109 | }, 110 | "404": { 111 | "description": "Not Found", 112 | "schema": { 113 | "type": "object", 114 | "additionalProperties": true 115 | } 116 | } 117 | } 118 | } 119 | }, 120 | "/comments": { 121 | "post": { 122 | "description": "Create a new comment", 123 | "consumes": [ 124 | "application/json" 125 | ], 126 | "produces": [ 127 | "application/json" 128 | ], 129 | "summary": "Create a new comment", 130 | "operationId": "create-comment", 131 | "parameters": [ 132 | { 133 | "description": "Comment object", 134 | "name": "comment", 135 | "in": "body", 136 | "required": true, 137 | "schema": { 138 | "$ref": "#/definitions/models.Comment" 139 | } 140 | } 141 | ], 142 | "responses": { 143 | "201": { 144 | "description": "Created", 145 | "schema": { 146 | "type": "object", 147 | "additionalProperties": true 148 | } 149 | }, 150 | "400": { 151 | "description": "Bad Request", 152 | "schema": { 153 | "type": "object", 154 | "additionalProperties": true 155 | } 156 | }, 157 | "401": { 158 | "description": "Unauthorized", 159 | "schema": { 160 | "type": "object", 161 | "additionalProperties": true 162 | } 163 | }, 164 | "500": { 165 | "description": "Internal Server Error", 166 | "schema": { 167 | "type": "object", 168 | "additionalProperties": true 169 | } 170 | } 171 | } 172 | } 173 | }, 174 | "/users/register": { 175 | "post": { 176 | "description": "Register a user", 177 | "consumes": [ 178 | "application/json" 179 | ], 180 | "produces": [ 181 | "application/json" 182 | ], 183 | "summary": "Register a user", 184 | "parameters": [ 185 | { 186 | "description": "Username", 187 | "name": "Username", 188 | "in": "body", 189 | "required": true, 190 | "schema": { 191 | "type": "string" 192 | } 193 | }, 194 | { 195 | "description": "Password", 196 | "name": "Password", 197 | "in": "body", 198 | "required": true, 199 | "schema": { 200 | "type": "string" 201 | } 202 | }, 203 | { 204 | "description": "Email", 205 | "name": "Email", 206 | "in": "body", 207 | "required": true, 208 | "schema": { 209 | "type": "string" 210 | } 211 | }, 212 | { 213 | "description": "FirstName", 214 | "name": "FirstName", 215 | "in": "body", 216 | "schema": { 217 | "type": "string" 218 | } 219 | }, 220 | { 221 | "description": "LastName", 222 | "name": "LastName", 223 | "in": "body", 224 | "schema": { 225 | "type": "string" 226 | } 227 | } 228 | ], 229 | "responses": { 230 | "201": { 231 | "description": "Created", 232 | "schema": { 233 | "type": "object", 234 | "additionalProperties": true 235 | } 236 | }, 237 | "400": { 238 | "description": "Bad Request", 239 | "schema": { 240 | "type": "object", 241 | "additionalProperties": true 242 | } 243 | }, 244 | "409": { 245 | "description": "Conflict", 246 | "schema": { 247 | "type": "object", 248 | "additionalProperties": true 249 | } 250 | } 251 | } 252 | } 253 | } 254 | }, 255 | "definitions": { 256 | "models.Category": { 257 | "type": "object" 258 | }, 259 | "models.Comment": { 260 | "type": "object" 261 | } 262 | } 263 | }` 264 | 265 | // SwaggerInfo holds exported Swagger Info so clients can modify it 266 | var SwaggerInfo = &swag.Spec{ 267 | Version: "1.0", 268 | Host: "localhost:8080", 269 | BasePath: "/api/v1", 270 | Schemes: []string{}, 271 | Title: "Blogo API server", 272 | Description: "A simple blog for educational purposes", 273 | InfoInstanceName: "swagger", 274 | SwaggerTemplate: docTemplate, 275 | LeftDelim: "{{", 276 | RightDelim: "}}", 277 | } 278 | 279 | func init() { 280 | swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) 281 | } 282 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= 3 | github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= 4 | github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= 5 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= 6 | github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= 7 | github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= 8 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= 9 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 10 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 11 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 12 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 13 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 15 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 | github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= 17 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 18 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 19 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 20 | github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 21 | github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 22 | github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= 23 | github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= 24 | github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= 25 | github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= 26 | github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= 27 | github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= 28 | github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8= 29 | github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= 30 | github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 31 | github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= 32 | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 33 | github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= 34 | github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 35 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 36 | github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= 37 | github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 38 | github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= 39 | github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= 40 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 41 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= 42 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= 43 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= 44 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= 45 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= 46 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= 47 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= 48 | github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= 49 | github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= 50 | github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= 51 | github.com/jackc/pgconn v1.12.1 h1:rsDFzIpRk7xT4B8FufgpCCeyjdNpKyghZeSefViE5W8= 52 | github.com/jackc/pgconn v1.12.1/go.mod h1:ZkhRC59Llhrq3oSfrikvwQ5NaxYExr6twkdkMLaKono= 53 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= 54 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= 55 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= 56 | github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= 57 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= 58 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= 59 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= 60 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= 61 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= 62 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= 63 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= 64 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= 65 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= 66 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 67 | github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 68 | github.com/jackc/pgproto3/v2 v2.3.0 h1:brH0pCGBDkBW07HWlN/oSBXrmo3WB0UvZd1pIuDcL8Y= 69 | github.com/jackc/pgproto3/v2 v2.3.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 70 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= 71 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= 72 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= 73 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= 74 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= 75 | github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= 76 | github.com/jackc/pgtype v1.11.0 h1:u4uiGPz/1hryuXzyaBhSk6dnIyyG2683olG2OV+UUgs= 77 | github.com/jackc/pgtype v1.11.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= 78 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= 79 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= 80 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= 81 | github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= 82 | github.com/jackc/pgx/v4 v4.16.1 h1:JzTglcal01DrghUqt+PmzWsZx/Yh7SC/CTQmSBMTd0Y= 83 | github.com/jackc/pgx/v4 v4.16.1/go.mod h1:SIhx0D5hoADaiXZVyv+3gSm3LCIIINTVO0PficsvWGQ= 84 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 85 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 86 | github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 87 | github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 88 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 89 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 90 | github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas= 91 | github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 92 | github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= 93 | github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 94 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 95 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 96 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 97 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 98 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 99 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 100 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 101 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 102 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 103 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= 104 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 105 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 106 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 107 | github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M= 108 | github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k= 109 | github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= 110 | github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= 111 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 112 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 113 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 114 | github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= 115 | github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 116 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 117 | github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 118 | github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 119 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 120 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 121 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= 122 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 123 | github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 124 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 125 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 126 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 127 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 128 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 129 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 130 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 131 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= 132 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 133 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 134 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 135 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 136 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 137 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 138 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 139 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 140 | github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= 141 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= 142 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= 143 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= 144 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 145 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= 146 | github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= 147 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 148 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 149 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 150 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 151 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 152 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 153 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 154 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 155 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 156 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 157 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 158 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 159 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 160 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 161 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 162 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 163 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 164 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 165 | github.com/swaggo/echo-swagger v1.4.1 h1:Yf0uPaJWp1uRtDloZALyLnvdBeoEL5Kc7DtnjzO/TUk= 166 | github.com/swaggo/echo-swagger v1.4.1/go.mod h1:C8bSi+9yH2FLZsnhqMZLIZddpUxZdBYuNHbtaS1Hljc= 167 | github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw= 168 | github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM= 169 | github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04= 170 | github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E= 171 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 172 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 173 | github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 174 | github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= 175 | github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 176 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= 177 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 178 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 179 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 180 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 181 | go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= 182 | go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 183 | go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= 184 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 185 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= 186 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 187 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 188 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 189 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 190 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 191 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 192 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= 193 | go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= 194 | go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= 195 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 196 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= 197 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 198 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 199 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 200 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 201 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 202 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 203 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 204 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 205 | golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= 206 | golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= 207 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 208 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 209 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 210 | golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= 211 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 212 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 213 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 214 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 215 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 216 | golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= 217 | golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= 218 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 219 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 220 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 221 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 222 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 223 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 224 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 225 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 226 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 227 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 228 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 229 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 230 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 231 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 232 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 233 | golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 234 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 235 | golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= 236 | golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 237 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 238 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 239 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 240 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 241 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 242 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 243 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 244 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 245 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 246 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 247 | golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 248 | golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 249 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 250 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 251 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 252 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 253 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 254 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 255 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 256 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 257 | golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= 258 | golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= 259 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 260 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 261 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 262 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 263 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 264 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 265 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 266 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 267 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 268 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 269 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 270 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= 271 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 272 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 273 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 274 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 275 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 276 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 277 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 278 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 279 | gorm.io/driver/postgres v1.3.7 h1:FKF6sIMDHDEvvMF/XJvbnCl0nu6KSKUaPXevJ4r+VYQ= 280 | gorm.io/driver/postgres v1.3.7/go.mod h1:f02ympjIcgtHEGFMZvdgTxODZ9snAHDb4hXfigBVuNI= 281 | gorm.io/gorm v1.23.4/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= 282 | gorm.io/gorm v1.23.5 h1:TnlF26wScKSvknUC/Rn8t0NLLM22fypYBlvj1+aH6dM= 283 | gorm.io/gorm v1.23.5/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= 284 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 285 | -------------------------------------------------------------------------------- /ui/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ui", 3 | "version": "0.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "ui", 9 | "version": "0.0.0", 10 | "devDependencies": { 11 | "@sveltejs/vite-plugin-svelte": "^1.0.0-next.30", 12 | "svelte": "^3.44.0", 13 | "vite": "^2.9.9" 14 | } 15 | }, 16 | "node_modules/@rollup/pluginutils": { 17 | "version": "4.2.1", 18 | "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", 19 | "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", 20 | "dev": true, 21 | "dependencies": { 22 | "estree-walker": "^2.0.1", 23 | "picomatch": "^2.2.2" 24 | }, 25 | "engines": { 26 | "node": ">= 8.0.0" 27 | } 28 | }, 29 | "node_modules/@sveltejs/vite-plugin-svelte": { 30 | "version": "1.0.0-next.49", 31 | "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-1.0.0-next.49.tgz", 32 | "integrity": "sha512-AKh0Ka8EDgidnxWUs8Hh2iZLZovkETkefO99XxZ4sW4WGJ7VFeBx5kH/NIIGlaNHLcrIvK3CK0HkZwC3Cici0A==", 33 | "dev": true, 34 | "dependencies": { 35 | "@rollup/pluginutils": "^4.2.1", 36 | "debug": "^4.3.4", 37 | "deepmerge": "^4.2.2", 38 | "kleur": "^4.1.4", 39 | "magic-string": "^0.26.2", 40 | "svelte-hmr": "^0.14.12" 41 | }, 42 | "engines": { 43 | "node": "^14.13.1 || >= 16" 44 | }, 45 | "peerDependencies": { 46 | "diff-match-patch": "^1.0.5", 47 | "svelte": "^3.44.0", 48 | "vite": "^2.9.0" 49 | }, 50 | "peerDependenciesMeta": { 51 | "diff-match-patch": { 52 | "optional": true 53 | } 54 | } 55 | }, 56 | "node_modules/debug": { 57 | "version": "4.3.4", 58 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 59 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 60 | "dev": true, 61 | "dependencies": { 62 | "ms": "2.1.2" 63 | }, 64 | "engines": { 65 | "node": ">=6.0" 66 | }, 67 | "peerDependenciesMeta": { 68 | "supports-color": { 69 | "optional": true 70 | } 71 | } 72 | }, 73 | "node_modules/deepmerge": { 74 | "version": "4.2.2", 75 | "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", 76 | "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", 77 | "dev": true, 78 | "engines": { 79 | "node": ">=0.10.0" 80 | } 81 | }, 82 | "node_modules/esbuild": { 83 | "version": "0.14.43", 84 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.43.tgz", 85 | "integrity": "sha512-Uf94+kQmy/5jsFwKWiQB4hfo/RkM9Dh7b79p8yqd1tshULdr25G2szLz631NoH3s2ujnKEKVD16RmOxvCNKRFA==", 86 | "dev": true, 87 | "hasInstallScript": true, 88 | "bin": { 89 | "esbuild": "bin/esbuild" 90 | }, 91 | "engines": { 92 | "node": ">=12" 93 | }, 94 | "optionalDependencies": { 95 | "esbuild-android-64": "0.14.43", 96 | "esbuild-android-arm64": "0.14.43", 97 | "esbuild-darwin-64": "0.14.43", 98 | "esbuild-darwin-arm64": "0.14.43", 99 | "esbuild-freebsd-64": "0.14.43", 100 | "esbuild-freebsd-arm64": "0.14.43", 101 | "esbuild-linux-32": "0.14.43", 102 | "esbuild-linux-64": "0.14.43", 103 | "esbuild-linux-arm": "0.14.43", 104 | "esbuild-linux-arm64": "0.14.43", 105 | "esbuild-linux-mips64le": "0.14.43", 106 | "esbuild-linux-ppc64le": "0.14.43", 107 | "esbuild-linux-riscv64": "0.14.43", 108 | "esbuild-linux-s390x": "0.14.43", 109 | "esbuild-netbsd-64": "0.14.43", 110 | "esbuild-openbsd-64": "0.14.43", 111 | "esbuild-sunos-64": "0.14.43", 112 | "esbuild-windows-32": "0.14.43", 113 | "esbuild-windows-64": "0.14.43", 114 | "esbuild-windows-arm64": "0.14.43" 115 | } 116 | }, 117 | "node_modules/esbuild-android-64": { 118 | "version": "0.14.43", 119 | "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.43.tgz", 120 | "integrity": "sha512-kqFXAS72K6cNrB6RiM7YJ5lNvmWRDSlpi7ZuRZ1hu1S3w0zlwcoCxWAyM23LQUyZSs1PbjHgdbbfYAN8IGh6xg==", 121 | "cpu": [ 122 | "x64" 123 | ], 124 | "dev": true, 125 | "optional": true, 126 | "os": [ 127 | "android" 128 | ], 129 | "engines": { 130 | "node": ">=12" 131 | } 132 | }, 133 | "node_modules/esbuild-android-arm64": { 134 | "version": "0.14.43", 135 | "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.43.tgz", 136 | "integrity": "sha512-bKS2BBFh+7XZY9rpjiHGRNA7LvWYbZWP87pLehggTG7tTaCDvj8qQGOU/OZSjCSKDYbgY7Q+oDw8RlYQ2Jt2BA==", 137 | "cpu": [ 138 | "arm64" 139 | ], 140 | "dev": true, 141 | "optional": true, 142 | "os": [ 143 | "android" 144 | ], 145 | "engines": { 146 | "node": ">=12" 147 | } 148 | }, 149 | "node_modules/esbuild-darwin-64": { 150 | "version": "0.14.43", 151 | "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.43.tgz", 152 | "integrity": "sha512-/3PSilx011ttoieRGkSZ0XV8zjBf2C9enV4ScMMbCT4dpx0mFhMOpFnCHkOK0pWGB8LklykFyHrWk2z6DENVUg==", 153 | "cpu": [ 154 | "x64" 155 | ], 156 | "dev": true, 157 | "optional": true, 158 | "os": [ 159 | "darwin" 160 | ], 161 | "engines": { 162 | "node": ">=12" 163 | } 164 | }, 165 | "node_modules/esbuild-darwin-arm64": { 166 | "version": "0.14.43", 167 | "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.43.tgz", 168 | "integrity": "sha512-1HyFUKs8DMCBOvw1Qxpr5Vv/ThNcVIFb5xgXWK3pyT40WPvgYIiRTwJCvNs4l8i5qWF8/CK5bQxJVDjQvtv0Yw==", 169 | "cpu": [ 170 | "arm64" 171 | ], 172 | "dev": true, 173 | "optional": true, 174 | "os": [ 175 | "darwin" 176 | ], 177 | "engines": { 178 | "node": ">=12" 179 | } 180 | }, 181 | "node_modules/esbuild-freebsd-64": { 182 | "version": "0.14.43", 183 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.43.tgz", 184 | "integrity": "sha512-FNWc05TPHYgaXjbPZO5/rJKSBslfG6BeMSs8GhwnqAKP56eEhvmzwnIz1QcC9cRVyO+IKqWNfmHFkCa1WJTULA==", 185 | "cpu": [ 186 | "x64" 187 | ], 188 | "dev": true, 189 | "optional": true, 190 | "os": [ 191 | "freebsd" 192 | ], 193 | "engines": { 194 | "node": ">=12" 195 | } 196 | }, 197 | "node_modules/esbuild-freebsd-arm64": { 198 | "version": "0.14.43", 199 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.43.tgz", 200 | "integrity": "sha512-amrYopclz3VohqisOPR6hA3GOWA3LZC1WDLnp21RhNmoERmJ/vLnOpnrG2P/Zao+/erKTCUqmrCIPVtj58DRoA==", 201 | "cpu": [ 202 | "arm64" 203 | ], 204 | "dev": true, 205 | "optional": true, 206 | "os": [ 207 | "freebsd" 208 | ], 209 | "engines": { 210 | "node": ">=12" 211 | } 212 | }, 213 | "node_modules/esbuild-linux-32": { 214 | "version": "0.14.43", 215 | "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.43.tgz", 216 | "integrity": "sha512-KoxoEra+9O3AKVvgDFvDkiuddCds6q71owSQEYwjtqRV7RwbPzKxJa6+uyzUulHcyGVq0g15K0oKG5CFBcvYDw==", 217 | "cpu": [ 218 | "ia32" 219 | ], 220 | "dev": true, 221 | "optional": true, 222 | "os": [ 223 | "linux" 224 | ], 225 | "engines": { 226 | "node": ">=12" 227 | } 228 | }, 229 | "node_modules/esbuild-linux-64": { 230 | "version": "0.14.43", 231 | "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.43.tgz", 232 | "integrity": "sha512-EwINwGMyiJMgBby5/SbMqKcUhS5AYAZ2CpEBzSowsJPNBJEdhkCTtEjk757TN/wxgbu3QklqDM6KghY660QCUw==", 233 | "cpu": [ 234 | "x64" 235 | ], 236 | "dev": true, 237 | "optional": true, 238 | "os": [ 239 | "linux" 240 | ], 241 | "engines": { 242 | "node": ">=12" 243 | } 244 | }, 245 | "node_modules/esbuild-linux-arm": { 246 | "version": "0.14.43", 247 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.43.tgz", 248 | "integrity": "sha512-e6YzQUoDxxtyamuF12eVzzRC7bbEFSZohJ6igQB9tBqnNmIQY3fI6Cns3z2wxtbZ3f2o6idkD2fQnlvs2902Dg==", 249 | "cpu": [ 250 | "arm" 251 | ], 252 | "dev": true, 253 | "optional": true, 254 | "os": [ 255 | "linux" 256 | ], 257 | "engines": { 258 | "node": ">=12" 259 | } 260 | }, 261 | "node_modules/esbuild-linux-arm64": { 262 | "version": "0.14.43", 263 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.43.tgz", 264 | "integrity": "sha512-UlSpjMWllAc70zYbHxWuDS3FJytyuR/gHJYBr8BICcTNb/TSOYVBg6U7b3jZ3mILTrgzwJUHwhEwK18FZDouUQ==", 265 | "cpu": [ 266 | "arm64" 267 | ], 268 | "dev": true, 269 | "optional": true, 270 | "os": [ 271 | "linux" 272 | ], 273 | "engines": { 274 | "node": ">=12" 275 | } 276 | }, 277 | "node_modules/esbuild-linux-mips64le": { 278 | "version": "0.14.43", 279 | "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.43.tgz", 280 | "integrity": "sha512-f+v8cInPEL1/SDP//CfSYzcDNgE4CY3xgDV81DWm3KAPWzhvxARrKxB1Pstf5mB56yAslJDxu7ryBUPX207EZA==", 281 | "cpu": [ 282 | "mips64el" 283 | ], 284 | "dev": true, 285 | "optional": true, 286 | "os": [ 287 | "linux" 288 | ], 289 | "engines": { 290 | "node": ">=12" 291 | } 292 | }, 293 | "node_modules/esbuild-linux-ppc64le": { 294 | "version": "0.14.43", 295 | "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.43.tgz", 296 | "integrity": "sha512-5wZYMDGAL/K2pqkdIsW+I4IR41kyfHr/QshJcNpUfK3RjB3VQcPWOaZmc+74rm4ZjVirYrtz+jWw0SgxtxRanA==", 297 | "cpu": [ 298 | "ppc64" 299 | ], 300 | "dev": true, 301 | "optional": true, 302 | "os": [ 303 | "linux" 304 | ], 305 | "engines": { 306 | "node": ">=12" 307 | } 308 | }, 309 | "node_modules/esbuild-linux-riscv64": { 310 | "version": "0.14.43", 311 | "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.43.tgz", 312 | "integrity": "sha512-lYcAOUxp85hC7lSjycJUVSmj4/9oEfSyXjb/ua9bNl8afonaduuqtw7hvKMoKuYnVwOCDw4RSfKpcnIRDWq+Bw==", 313 | "cpu": [ 314 | "riscv64" 315 | ], 316 | "dev": true, 317 | "optional": true, 318 | "os": [ 319 | "linux" 320 | ], 321 | "engines": { 322 | "node": ">=12" 323 | } 324 | }, 325 | "node_modules/esbuild-linux-s390x": { 326 | "version": "0.14.43", 327 | "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.43.tgz", 328 | "integrity": "sha512-27e43ZhHvhFE4nM7HqtUbMRu37I/4eNSUbb8FGZWszV+uLzMIsHDwLoBiJmw7G9N+hrehNPeQ4F5Ujad0DrUKQ==", 329 | "cpu": [ 330 | "s390x" 331 | ], 332 | "dev": true, 333 | "optional": true, 334 | "os": [ 335 | "linux" 336 | ], 337 | "engines": { 338 | "node": ">=12" 339 | } 340 | }, 341 | "node_modules/esbuild-netbsd-64": { 342 | "version": "0.14.43", 343 | "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.43.tgz", 344 | "integrity": "sha512-2mH4QF6hHBn5zzAfxEI/2eBC0mspVsZ6UVo821LpAJKMvLJPBk3XJO5xwg7paDqSqpl7p6IRrAenW999AEfJhQ==", 345 | "cpu": [ 346 | "x64" 347 | ], 348 | "dev": true, 349 | "optional": true, 350 | "os": [ 351 | "netbsd" 352 | ], 353 | "engines": { 354 | "node": ">=12" 355 | } 356 | }, 357 | "node_modules/esbuild-openbsd-64": { 358 | "version": "0.14.43", 359 | "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.43.tgz", 360 | "integrity": "sha512-ZhQpiZjvqCqO8jKdGp9+8k9E/EHSA+zIWOg+grwZasI9RoblqJ1QiZqqi7jfd6ZrrG1UFBNGe4m0NFxCFbMVbg==", 361 | "cpu": [ 362 | "x64" 363 | ], 364 | "dev": true, 365 | "optional": true, 366 | "os": [ 367 | "openbsd" 368 | ], 369 | "engines": { 370 | "node": ">=12" 371 | } 372 | }, 373 | "node_modules/esbuild-sunos-64": { 374 | "version": "0.14.43", 375 | "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.43.tgz", 376 | "integrity": "sha512-DgxSi9DaHReL9gYuul2rrQCAapgnCJkh3LSHPKsY26zytYppG0HgkgVF80zjIlvEsUbGBP/GHQzBtrezj/Zq1Q==", 377 | "cpu": [ 378 | "x64" 379 | ], 380 | "dev": true, 381 | "optional": true, 382 | "os": [ 383 | "sunos" 384 | ], 385 | "engines": { 386 | "node": ">=12" 387 | } 388 | }, 389 | "node_modules/esbuild-windows-32": { 390 | "version": "0.14.43", 391 | "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.43.tgz", 392 | "integrity": "sha512-Ih3+2O5oExiqm0mY6YYE5dR0o8+AspccQ3vIAtRodwFvhuyGLjb0Hbmzun/F3Lw19nuhPMu3sW2fqIJ5xBxByw==", 393 | "cpu": [ 394 | "ia32" 395 | ], 396 | "dev": true, 397 | "optional": true, 398 | "os": [ 399 | "win32" 400 | ], 401 | "engines": { 402 | "node": ">=12" 403 | } 404 | }, 405 | "node_modules/esbuild-windows-64": { 406 | "version": "0.14.43", 407 | "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.43.tgz", 408 | "integrity": "sha512-8NsuNfI8xwFuJbrCuI+aBqNTYkrWErejFO5aYM+yHqyHuL8mmepLS9EPzAzk8rvfaJrhN0+RvKWAcymViHOKEw==", 409 | "cpu": [ 410 | "x64" 411 | ], 412 | "dev": true, 413 | "optional": true, 414 | "os": [ 415 | "win32" 416 | ], 417 | "engines": { 418 | "node": ">=12" 419 | } 420 | }, 421 | "node_modules/esbuild-windows-arm64": { 422 | "version": "0.14.43", 423 | "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.43.tgz", 424 | "integrity": "sha512-7ZlD7bo++kVRblJEoG+cepljkfP8bfuTPz5fIXzptwnPaFwGS6ahvfoYzY7WCf5v/1nX2X02HDraVItTgbHnKw==", 425 | "cpu": [ 426 | "arm64" 427 | ], 428 | "dev": true, 429 | "optional": true, 430 | "os": [ 431 | "win32" 432 | ], 433 | "engines": { 434 | "node": ">=12" 435 | } 436 | }, 437 | "node_modules/estree-walker": { 438 | "version": "2.0.2", 439 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 440 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", 441 | "dev": true 442 | }, 443 | "node_modules/fsevents": { 444 | "version": "2.3.2", 445 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 446 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 447 | "dev": true, 448 | "hasInstallScript": true, 449 | "optional": true, 450 | "os": [ 451 | "darwin" 452 | ], 453 | "engines": { 454 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 455 | } 456 | }, 457 | "node_modules/function-bind": { 458 | "version": "1.1.1", 459 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 460 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 461 | "dev": true 462 | }, 463 | "node_modules/has": { 464 | "version": "1.0.3", 465 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 466 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 467 | "dev": true, 468 | "dependencies": { 469 | "function-bind": "^1.1.1" 470 | }, 471 | "engines": { 472 | "node": ">= 0.4.0" 473 | } 474 | }, 475 | "node_modules/is-core-module": { 476 | "version": "2.9.0", 477 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", 478 | "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", 479 | "dev": true, 480 | "dependencies": { 481 | "has": "^1.0.3" 482 | }, 483 | "funding": { 484 | "url": "https://github.com/sponsors/ljharb" 485 | } 486 | }, 487 | "node_modules/kleur": { 488 | "version": "4.1.4", 489 | "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.4.tgz", 490 | "integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==", 491 | "dev": true, 492 | "engines": { 493 | "node": ">=6" 494 | } 495 | }, 496 | "node_modules/magic-string": { 497 | "version": "0.26.2", 498 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.2.tgz", 499 | "integrity": "sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A==", 500 | "dev": true, 501 | "dependencies": { 502 | "sourcemap-codec": "^1.4.8" 503 | }, 504 | "engines": { 505 | "node": ">=12" 506 | } 507 | }, 508 | "node_modules/ms": { 509 | "version": "2.1.2", 510 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 511 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 512 | "dev": true 513 | }, 514 | "node_modules/nanoid": { 515 | "version": "3.3.4", 516 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", 517 | "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", 518 | "dev": true, 519 | "bin": { 520 | "nanoid": "bin/nanoid.cjs" 521 | }, 522 | "engines": { 523 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 524 | } 525 | }, 526 | "node_modules/path-parse": { 527 | "version": "1.0.7", 528 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 529 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 530 | "dev": true 531 | }, 532 | "node_modules/picocolors": { 533 | "version": "1.0.0", 534 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 535 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", 536 | "dev": true 537 | }, 538 | "node_modules/picomatch": { 539 | "version": "2.3.1", 540 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 541 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 542 | "dev": true, 543 | "engines": { 544 | "node": ">=8.6" 545 | }, 546 | "funding": { 547 | "url": "https://github.com/sponsors/jonschlinkert" 548 | } 549 | }, 550 | "node_modules/postcss": { 551 | "version": "8.4.14", 552 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", 553 | "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", 554 | "dev": true, 555 | "funding": [ 556 | { 557 | "type": "opencollective", 558 | "url": "https://opencollective.com/postcss/" 559 | }, 560 | { 561 | "type": "tidelift", 562 | "url": "https://tidelift.com/funding/github/npm/postcss" 563 | } 564 | ], 565 | "dependencies": { 566 | "nanoid": "^3.3.4", 567 | "picocolors": "^1.0.0", 568 | "source-map-js": "^1.0.2" 569 | }, 570 | "engines": { 571 | "node": "^10 || ^12 || >=14" 572 | } 573 | }, 574 | "node_modules/resolve": { 575 | "version": "1.22.0", 576 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", 577 | "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", 578 | "dev": true, 579 | "dependencies": { 580 | "is-core-module": "^2.8.1", 581 | "path-parse": "^1.0.7", 582 | "supports-preserve-symlinks-flag": "^1.0.0" 583 | }, 584 | "bin": { 585 | "resolve": "bin/resolve" 586 | }, 587 | "funding": { 588 | "url": "https://github.com/sponsors/ljharb" 589 | } 590 | }, 591 | "node_modules/rollup": { 592 | "version": "2.75.6", 593 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.75.6.tgz", 594 | "integrity": "sha512-OEf0TgpC9vU6WGROJIk1JA3LR5vk/yvqlzxqdrE2CzzXnqKXNzbAwlWUXis8RS3ZPe7LAq+YUxsRa0l3r27MLA==", 595 | "dev": true, 596 | "bin": { 597 | "rollup": "dist/bin/rollup" 598 | }, 599 | "engines": { 600 | "node": ">=10.0.0" 601 | }, 602 | "optionalDependencies": { 603 | "fsevents": "~2.3.2" 604 | } 605 | }, 606 | "node_modules/source-map-js": { 607 | "version": "1.0.2", 608 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", 609 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", 610 | "dev": true, 611 | "engines": { 612 | "node": ">=0.10.0" 613 | } 614 | }, 615 | "node_modules/sourcemap-codec": { 616 | "version": "1.4.8", 617 | "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", 618 | "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", 619 | "dev": true 620 | }, 621 | "node_modules/supports-preserve-symlinks-flag": { 622 | "version": "1.0.0", 623 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 624 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 625 | "dev": true, 626 | "engines": { 627 | "node": ">= 0.4" 628 | }, 629 | "funding": { 630 | "url": "https://github.com/sponsors/ljharb" 631 | } 632 | }, 633 | "node_modules/svelte": { 634 | "version": "3.48.0", 635 | "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.48.0.tgz", 636 | "integrity": "sha512-fN2YRm/bGumvjUpu6yI3BpvZnpIm9I6A7HR4oUNYd7ggYyIwSA/BX7DJ+UXXffLp6XNcUijyLvttbPVCYa/3xQ==", 637 | "dev": true, 638 | "engines": { 639 | "node": ">= 8" 640 | } 641 | }, 642 | "node_modules/svelte-hmr": { 643 | "version": "0.14.12", 644 | "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.14.12.tgz", 645 | "integrity": "sha512-4QSW/VvXuqVcFZ+RhxiR8/newmwOCTlbYIezvkeN6302YFRE8cXy0naamHcjz8Y9Ce3ITTZtrHrIL0AGfyo61w==", 646 | "dev": true, 647 | "engines": { 648 | "node": "^12.20 || ^14.13.1 || >= 16" 649 | }, 650 | "peerDependencies": { 651 | "svelte": ">=3.19.0" 652 | } 653 | }, 654 | "node_modules/vite": { 655 | "version": "2.9.12", 656 | "resolved": "https://registry.npmjs.org/vite/-/vite-2.9.12.tgz", 657 | "integrity": "sha512-suxC36dQo9Rq1qMB2qiRorNJtJAdxguu5TMvBHOc/F370KvqAe9t48vYp+/TbPKRNrMh/J55tOUmkuIqstZaew==", 658 | "dev": true, 659 | "dependencies": { 660 | "esbuild": "^0.14.27", 661 | "postcss": "^8.4.13", 662 | "resolve": "^1.22.0", 663 | "rollup": "^2.59.0" 664 | }, 665 | "bin": { 666 | "vite": "bin/vite.js" 667 | }, 668 | "engines": { 669 | "node": ">=12.2.0" 670 | }, 671 | "optionalDependencies": { 672 | "fsevents": "~2.3.2" 673 | }, 674 | "peerDependencies": { 675 | "less": "*", 676 | "sass": "*", 677 | "stylus": "*" 678 | }, 679 | "peerDependenciesMeta": { 680 | "less": { 681 | "optional": true 682 | }, 683 | "sass": { 684 | "optional": true 685 | }, 686 | "stylus": { 687 | "optional": true 688 | } 689 | } 690 | } 691 | }, 692 | "dependencies": { 693 | "@rollup/pluginutils": { 694 | "version": "4.2.1", 695 | "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", 696 | "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", 697 | "dev": true, 698 | "requires": { 699 | "estree-walker": "^2.0.1", 700 | "picomatch": "^2.2.2" 701 | } 702 | }, 703 | "@sveltejs/vite-plugin-svelte": { 704 | "version": "1.0.0-next.49", 705 | "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-1.0.0-next.49.tgz", 706 | "integrity": "sha512-AKh0Ka8EDgidnxWUs8Hh2iZLZovkETkefO99XxZ4sW4WGJ7VFeBx5kH/NIIGlaNHLcrIvK3CK0HkZwC3Cici0A==", 707 | "dev": true, 708 | "requires": { 709 | "@rollup/pluginutils": "^4.2.1", 710 | "debug": "^4.3.4", 711 | "deepmerge": "^4.2.2", 712 | "kleur": "^4.1.4", 713 | "magic-string": "^0.26.2", 714 | "svelte-hmr": "^0.14.12" 715 | } 716 | }, 717 | "debug": { 718 | "version": "4.3.4", 719 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 720 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 721 | "dev": true, 722 | "requires": { 723 | "ms": "2.1.2" 724 | } 725 | }, 726 | "deepmerge": { 727 | "version": "4.2.2", 728 | "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", 729 | "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", 730 | "dev": true 731 | }, 732 | "esbuild": { 733 | "version": "0.14.43", 734 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.43.tgz", 735 | "integrity": "sha512-Uf94+kQmy/5jsFwKWiQB4hfo/RkM9Dh7b79p8yqd1tshULdr25G2szLz631NoH3s2ujnKEKVD16RmOxvCNKRFA==", 736 | "dev": true, 737 | "requires": { 738 | "esbuild-android-64": "0.14.43", 739 | "esbuild-android-arm64": "0.14.43", 740 | "esbuild-darwin-64": "0.14.43", 741 | "esbuild-darwin-arm64": "0.14.43", 742 | "esbuild-freebsd-64": "0.14.43", 743 | "esbuild-freebsd-arm64": "0.14.43", 744 | "esbuild-linux-32": "0.14.43", 745 | "esbuild-linux-64": "0.14.43", 746 | "esbuild-linux-arm": "0.14.43", 747 | "esbuild-linux-arm64": "0.14.43", 748 | "esbuild-linux-mips64le": "0.14.43", 749 | "esbuild-linux-ppc64le": "0.14.43", 750 | "esbuild-linux-riscv64": "0.14.43", 751 | "esbuild-linux-s390x": "0.14.43", 752 | "esbuild-netbsd-64": "0.14.43", 753 | "esbuild-openbsd-64": "0.14.43", 754 | "esbuild-sunos-64": "0.14.43", 755 | "esbuild-windows-32": "0.14.43", 756 | "esbuild-windows-64": "0.14.43", 757 | "esbuild-windows-arm64": "0.14.43" 758 | } 759 | }, 760 | "esbuild-android-64": { 761 | "version": "0.14.43", 762 | "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.43.tgz", 763 | "integrity": "sha512-kqFXAS72K6cNrB6RiM7YJ5lNvmWRDSlpi7ZuRZ1hu1S3w0zlwcoCxWAyM23LQUyZSs1PbjHgdbbfYAN8IGh6xg==", 764 | "dev": true, 765 | "optional": true 766 | }, 767 | "esbuild-android-arm64": { 768 | "version": "0.14.43", 769 | "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.43.tgz", 770 | "integrity": "sha512-bKS2BBFh+7XZY9rpjiHGRNA7LvWYbZWP87pLehggTG7tTaCDvj8qQGOU/OZSjCSKDYbgY7Q+oDw8RlYQ2Jt2BA==", 771 | "dev": true, 772 | "optional": true 773 | }, 774 | "esbuild-darwin-64": { 775 | "version": "0.14.43", 776 | "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.43.tgz", 777 | "integrity": "sha512-/3PSilx011ttoieRGkSZ0XV8zjBf2C9enV4ScMMbCT4dpx0mFhMOpFnCHkOK0pWGB8LklykFyHrWk2z6DENVUg==", 778 | "dev": true, 779 | "optional": true 780 | }, 781 | "esbuild-darwin-arm64": { 782 | "version": "0.14.43", 783 | "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.43.tgz", 784 | "integrity": "sha512-1HyFUKs8DMCBOvw1Qxpr5Vv/ThNcVIFb5xgXWK3pyT40WPvgYIiRTwJCvNs4l8i5qWF8/CK5bQxJVDjQvtv0Yw==", 785 | "dev": true, 786 | "optional": true 787 | }, 788 | "esbuild-freebsd-64": { 789 | "version": "0.14.43", 790 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.43.tgz", 791 | "integrity": "sha512-FNWc05TPHYgaXjbPZO5/rJKSBslfG6BeMSs8GhwnqAKP56eEhvmzwnIz1QcC9cRVyO+IKqWNfmHFkCa1WJTULA==", 792 | "dev": true, 793 | "optional": true 794 | }, 795 | "esbuild-freebsd-arm64": { 796 | "version": "0.14.43", 797 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.43.tgz", 798 | "integrity": "sha512-amrYopclz3VohqisOPR6hA3GOWA3LZC1WDLnp21RhNmoERmJ/vLnOpnrG2P/Zao+/erKTCUqmrCIPVtj58DRoA==", 799 | "dev": true, 800 | "optional": true 801 | }, 802 | "esbuild-linux-32": { 803 | "version": "0.14.43", 804 | "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.43.tgz", 805 | "integrity": "sha512-KoxoEra+9O3AKVvgDFvDkiuddCds6q71owSQEYwjtqRV7RwbPzKxJa6+uyzUulHcyGVq0g15K0oKG5CFBcvYDw==", 806 | "dev": true, 807 | "optional": true 808 | }, 809 | "esbuild-linux-64": { 810 | "version": "0.14.43", 811 | "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.43.tgz", 812 | "integrity": "sha512-EwINwGMyiJMgBby5/SbMqKcUhS5AYAZ2CpEBzSowsJPNBJEdhkCTtEjk757TN/wxgbu3QklqDM6KghY660QCUw==", 813 | "dev": true, 814 | "optional": true 815 | }, 816 | "esbuild-linux-arm": { 817 | "version": "0.14.43", 818 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.43.tgz", 819 | "integrity": "sha512-e6YzQUoDxxtyamuF12eVzzRC7bbEFSZohJ6igQB9tBqnNmIQY3fI6Cns3z2wxtbZ3f2o6idkD2fQnlvs2902Dg==", 820 | "dev": true, 821 | "optional": true 822 | }, 823 | "esbuild-linux-arm64": { 824 | "version": "0.14.43", 825 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.43.tgz", 826 | "integrity": "sha512-UlSpjMWllAc70zYbHxWuDS3FJytyuR/gHJYBr8BICcTNb/TSOYVBg6U7b3jZ3mILTrgzwJUHwhEwK18FZDouUQ==", 827 | "dev": true, 828 | "optional": true 829 | }, 830 | "esbuild-linux-mips64le": { 831 | "version": "0.14.43", 832 | "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.43.tgz", 833 | "integrity": "sha512-f+v8cInPEL1/SDP//CfSYzcDNgE4CY3xgDV81DWm3KAPWzhvxARrKxB1Pstf5mB56yAslJDxu7ryBUPX207EZA==", 834 | "dev": true, 835 | "optional": true 836 | }, 837 | "esbuild-linux-ppc64le": { 838 | "version": "0.14.43", 839 | "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.43.tgz", 840 | "integrity": "sha512-5wZYMDGAL/K2pqkdIsW+I4IR41kyfHr/QshJcNpUfK3RjB3VQcPWOaZmc+74rm4ZjVirYrtz+jWw0SgxtxRanA==", 841 | "dev": true, 842 | "optional": true 843 | }, 844 | "esbuild-linux-riscv64": { 845 | "version": "0.14.43", 846 | "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.43.tgz", 847 | "integrity": "sha512-lYcAOUxp85hC7lSjycJUVSmj4/9oEfSyXjb/ua9bNl8afonaduuqtw7hvKMoKuYnVwOCDw4RSfKpcnIRDWq+Bw==", 848 | "dev": true, 849 | "optional": true 850 | }, 851 | "esbuild-linux-s390x": { 852 | "version": "0.14.43", 853 | "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.43.tgz", 854 | "integrity": "sha512-27e43ZhHvhFE4nM7HqtUbMRu37I/4eNSUbb8FGZWszV+uLzMIsHDwLoBiJmw7G9N+hrehNPeQ4F5Ujad0DrUKQ==", 855 | "dev": true, 856 | "optional": true 857 | }, 858 | "esbuild-netbsd-64": { 859 | "version": "0.14.43", 860 | "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.43.tgz", 861 | "integrity": "sha512-2mH4QF6hHBn5zzAfxEI/2eBC0mspVsZ6UVo821LpAJKMvLJPBk3XJO5xwg7paDqSqpl7p6IRrAenW999AEfJhQ==", 862 | "dev": true, 863 | "optional": true 864 | }, 865 | "esbuild-openbsd-64": { 866 | "version": "0.14.43", 867 | "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.43.tgz", 868 | "integrity": "sha512-ZhQpiZjvqCqO8jKdGp9+8k9E/EHSA+zIWOg+grwZasI9RoblqJ1QiZqqi7jfd6ZrrG1UFBNGe4m0NFxCFbMVbg==", 869 | "dev": true, 870 | "optional": true 871 | }, 872 | "esbuild-sunos-64": { 873 | "version": "0.14.43", 874 | "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.43.tgz", 875 | "integrity": "sha512-DgxSi9DaHReL9gYuul2rrQCAapgnCJkh3LSHPKsY26zytYppG0HgkgVF80zjIlvEsUbGBP/GHQzBtrezj/Zq1Q==", 876 | "dev": true, 877 | "optional": true 878 | }, 879 | "esbuild-windows-32": { 880 | "version": "0.14.43", 881 | "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.43.tgz", 882 | "integrity": "sha512-Ih3+2O5oExiqm0mY6YYE5dR0o8+AspccQ3vIAtRodwFvhuyGLjb0Hbmzun/F3Lw19nuhPMu3sW2fqIJ5xBxByw==", 883 | "dev": true, 884 | "optional": true 885 | }, 886 | "esbuild-windows-64": { 887 | "version": "0.14.43", 888 | "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.43.tgz", 889 | "integrity": "sha512-8NsuNfI8xwFuJbrCuI+aBqNTYkrWErejFO5aYM+yHqyHuL8mmepLS9EPzAzk8rvfaJrhN0+RvKWAcymViHOKEw==", 890 | "dev": true, 891 | "optional": true 892 | }, 893 | "esbuild-windows-arm64": { 894 | "version": "0.14.43", 895 | "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.43.tgz", 896 | "integrity": "sha512-7ZlD7bo++kVRblJEoG+cepljkfP8bfuTPz5fIXzptwnPaFwGS6ahvfoYzY7WCf5v/1nX2X02HDraVItTgbHnKw==", 897 | "dev": true, 898 | "optional": true 899 | }, 900 | "estree-walker": { 901 | "version": "2.0.2", 902 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 903 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", 904 | "dev": true 905 | }, 906 | "fsevents": { 907 | "version": "2.3.2", 908 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 909 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 910 | "dev": true, 911 | "optional": true 912 | }, 913 | "function-bind": { 914 | "version": "1.1.1", 915 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 916 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 917 | "dev": true 918 | }, 919 | "has": { 920 | "version": "1.0.3", 921 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 922 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 923 | "dev": true, 924 | "requires": { 925 | "function-bind": "^1.1.1" 926 | } 927 | }, 928 | "is-core-module": { 929 | "version": "2.9.0", 930 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", 931 | "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", 932 | "dev": true, 933 | "requires": { 934 | "has": "^1.0.3" 935 | } 936 | }, 937 | "kleur": { 938 | "version": "4.1.4", 939 | "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.4.tgz", 940 | "integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==", 941 | "dev": true 942 | }, 943 | "magic-string": { 944 | "version": "0.26.2", 945 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.2.tgz", 946 | "integrity": "sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A==", 947 | "dev": true, 948 | "requires": { 949 | "sourcemap-codec": "^1.4.8" 950 | } 951 | }, 952 | "ms": { 953 | "version": "2.1.2", 954 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 955 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 956 | "dev": true 957 | }, 958 | "nanoid": { 959 | "version": "3.3.4", 960 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", 961 | "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", 962 | "dev": true 963 | }, 964 | "path-parse": { 965 | "version": "1.0.7", 966 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 967 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 968 | "dev": true 969 | }, 970 | "picocolors": { 971 | "version": "1.0.0", 972 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 973 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", 974 | "dev": true 975 | }, 976 | "picomatch": { 977 | "version": "2.3.1", 978 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 979 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 980 | "dev": true 981 | }, 982 | "postcss": { 983 | "version": "8.4.14", 984 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", 985 | "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", 986 | "dev": true, 987 | "requires": { 988 | "nanoid": "^3.3.4", 989 | "picocolors": "^1.0.0", 990 | "source-map-js": "^1.0.2" 991 | } 992 | }, 993 | "resolve": { 994 | "version": "1.22.0", 995 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", 996 | "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", 997 | "dev": true, 998 | "requires": { 999 | "is-core-module": "^2.8.1", 1000 | "path-parse": "^1.0.7", 1001 | "supports-preserve-symlinks-flag": "^1.0.0" 1002 | } 1003 | }, 1004 | "rollup": { 1005 | "version": "2.75.6", 1006 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.75.6.tgz", 1007 | "integrity": "sha512-OEf0TgpC9vU6WGROJIk1JA3LR5vk/yvqlzxqdrE2CzzXnqKXNzbAwlWUXis8RS3ZPe7LAq+YUxsRa0l3r27MLA==", 1008 | "dev": true, 1009 | "requires": { 1010 | "fsevents": "~2.3.2" 1011 | } 1012 | }, 1013 | "source-map-js": { 1014 | "version": "1.0.2", 1015 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", 1016 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", 1017 | "dev": true 1018 | }, 1019 | "sourcemap-codec": { 1020 | "version": "1.4.8", 1021 | "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", 1022 | "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", 1023 | "dev": true 1024 | }, 1025 | "supports-preserve-symlinks-flag": { 1026 | "version": "1.0.0", 1027 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 1028 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 1029 | "dev": true 1030 | }, 1031 | "svelte": { 1032 | "version": "3.48.0", 1033 | "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.48.0.tgz", 1034 | "integrity": "sha512-fN2YRm/bGumvjUpu6yI3BpvZnpIm9I6A7HR4oUNYd7ggYyIwSA/BX7DJ+UXXffLp6XNcUijyLvttbPVCYa/3xQ==", 1035 | "dev": true 1036 | }, 1037 | "svelte-hmr": { 1038 | "version": "0.14.12", 1039 | "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.14.12.tgz", 1040 | "integrity": "sha512-4QSW/VvXuqVcFZ+RhxiR8/newmwOCTlbYIezvkeN6302YFRE8cXy0naamHcjz8Y9Ce3ITTZtrHrIL0AGfyo61w==", 1041 | "dev": true, 1042 | "requires": {} 1043 | }, 1044 | "vite": { 1045 | "version": "2.9.12", 1046 | "resolved": "https://registry.npmjs.org/vite/-/vite-2.9.12.tgz", 1047 | "integrity": "sha512-suxC36dQo9Rq1qMB2qiRorNJtJAdxguu5TMvBHOc/F370KvqAe9t48vYp+/TbPKRNrMh/J55tOUmkuIqstZaew==", 1048 | "dev": true, 1049 | "requires": { 1050 | "esbuild": "^0.14.27", 1051 | "fsevents": "~2.3.2", 1052 | "postcss": "^8.4.13", 1053 | "resolve": "^1.22.0", 1054 | "rollup": "^2.59.0" 1055 | } 1056 | } 1057 | } 1058 | } 1059 | --------------------------------------------------------------------------------