mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	Validate migration files (#18203)
JSON Schema validation for data used by Gitea during migrations Discussion at https://forum.forgefriends.org/t/common-json-schema-for-repository-information/563 Co-authored-by: Loïc Dachary <loic@dachary.org>
This commit is contained in:
		
				
					committed by
					
						
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							49dd906753
						
					
				
				
					commit
					3bb028cc46
				
			
							
								
								
									
										112
									
								
								modules/migration/file_format.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								modules/migration/file_format.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,112 @@
 | 
			
		||||
// Copyright 2022 The Gitea Authors. All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a MIT-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
package migration
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/json"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
 | 
			
		||||
	"github.com/santhosh-tekuri/jsonschema/v5"
 | 
			
		||||
	"gopkg.in/yaml.v2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Load project data from file, with optional validation
 | 
			
		||||
func Load(filename string, data interface{}, validation bool) error {
 | 
			
		||||
	isJSON := strings.HasSuffix(filename, ".json")
 | 
			
		||||
 | 
			
		||||
	bs, err := os.ReadFile(filename)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if validation {
 | 
			
		||||
		err := validate(bs, data, isJSON)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return unmarshal(bs, data, isJSON)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func unmarshal(bs []byte, data interface{}, isJSON bool) error {
 | 
			
		||||
	if isJSON {
 | 
			
		||||
		return json.Unmarshal(bs, data)
 | 
			
		||||
	}
 | 
			
		||||
	return yaml.Unmarshal(bs, data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getSchema(filename string) (*jsonschema.Schema, error) {
 | 
			
		||||
	c := jsonschema.NewCompiler()
 | 
			
		||||
	c.LoadURL = openSchema
 | 
			
		||||
	return c.Compile(filename)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func validate(bs []byte, datatype interface{}, isJSON bool) error {
 | 
			
		||||
	var v interface{}
 | 
			
		||||
	err := unmarshal(bs, &v, isJSON)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if !isJSON {
 | 
			
		||||
		v, err = toStringKeys(v)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var schemaFilename string
 | 
			
		||||
	switch datatype := datatype.(type) {
 | 
			
		||||
	case *[]*Issue:
 | 
			
		||||
		schemaFilename = "issue.json"
 | 
			
		||||
	case *[]*Milestone:
 | 
			
		||||
		schemaFilename = "milestone.json"
 | 
			
		||||
	default:
 | 
			
		||||
		return fmt.Errorf("file_format:validate: %T has not a validation implemented", datatype)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sch, err := getSchema(schemaFilename)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	err = sch.Validate(v)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error("migration validation with %s failed for\n%s", schemaFilename, string(bs))
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toStringKeys(val interface{}) (interface{}, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
	switch val := val.(type) {
 | 
			
		||||
	case map[interface{}]interface{}:
 | 
			
		||||
		m := make(map[string]interface{})
 | 
			
		||||
		for k, v := range val {
 | 
			
		||||
			k, ok := k.(string)
 | 
			
		||||
			if !ok {
 | 
			
		||||
				return nil, fmt.Errorf("found non-string key %T %s", k, k)
 | 
			
		||||
			}
 | 
			
		||||
			m[k], err = toStringKeys(v)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return m, nil
 | 
			
		||||
	case []interface{}:
 | 
			
		||||
		l := make([]interface{}, len(val))
 | 
			
		||||
		for i, v := range val {
 | 
			
		||||
			l[i], err = toStringKeys(v)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return l, nil
 | 
			
		||||
	default:
 | 
			
		||||
		return val, nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								modules/migration/file_format_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								modules/migration/file_format_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
// Copyright 2022 The Gitea Authors. All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a MIT-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
package migration
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/santhosh-tekuri/jsonschema/v5"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestMigrationJSON_IssueOK(t *testing.T) {
 | 
			
		||||
	issues := make([]*Issue, 0, 10)
 | 
			
		||||
	err := Load("file_format_testdata/issue_a.json", &issues, true)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	err = Load("file_format_testdata/issue_a.yml", &issues, true)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMigrationJSON_IssueFail(t *testing.T) {
 | 
			
		||||
	issues := make([]*Issue, 0, 10)
 | 
			
		||||
	err := Load("file_format_testdata/issue_b.json", &issues, true)
 | 
			
		||||
	if _, ok := err.(*jsonschema.ValidationError); ok {
 | 
			
		||||
		errors := strings.Split(err.(*jsonschema.ValidationError).GoString(), "\n")
 | 
			
		||||
		assert.Contains(t, errors[1], "missing properties")
 | 
			
		||||
		assert.Contains(t, errors[1], "poster_id")
 | 
			
		||||
	} else {
 | 
			
		||||
		t.Fatalf("got: type %T with value %s, want: *jsonschema.ValidationError", err, err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMigrationJSON_MilestoneOK(t *testing.T) {
 | 
			
		||||
	milestones := make([]*Milestone, 0, 10)
 | 
			
		||||
	err := Load("file_format_testdata/milestones.json", &milestones, true)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								modules/migration/file_format_testdata/issue_a.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								modules/migration/file_format_testdata/issue_a.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
[
 | 
			
		||||
  {
 | 
			
		||||
      "number": 1,
 | 
			
		||||
      "poster_id": 1,
 | 
			
		||||
      "poster_name": "name_a",
 | 
			
		||||
      "title": "title_a",
 | 
			
		||||
      "content": "content_a",
 | 
			
		||||
      "state": "closed",
 | 
			
		||||
      "is_locked": false,
 | 
			
		||||
      "created": "1985-04-12T23:20:50.52Z",
 | 
			
		||||
      "updated": "1986-04-12T23:20:50.52Z",
 | 
			
		||||
      "closed": "1987-04-12T23:20:50.52Z"
 | 
			
		||||
  }
 | 
			
		||||
]
 | 
			
		||||
							
								
								
									
										10
									
								
								modules/migration/file_format_testdata/issue_a.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								modules/migration/file_format_testdata/issue_a.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
- number: 1
 | 
			
		||||
  poster_id: 1
 | 
			
		||||
  poster_name: name_a
 | 
			
		||||
  title: title_a
 | 
			
		||||
  content: content_a
 | 
			
		||||
  state: closed
 | 
			
		||||
  is_locked: false
 | 
			
		||||
  created: 2021-05-27T15:24:13+02:00
 | 
			
		||||
  updated: 2021-11-11T10:52:45+01:00
 | 
			
		||||
  closed: 2021-11-11T10:52:45+01:00
 | 
			
		||||
							
								
								
									
										5
									
								
								modules/migration/file_format_testdata/issue_b.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								modules/migration/file_format_testdata/issue_b.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
[
 | 
			
		||||
  {
 | 
			
		||||
      "number": 1
 | 
			
		||||
  }
 | 
			
		||||
]
 | 
			
		||||
							
								
								
									
										20
									
								
								modules/migration/file_format_testdata/milestones.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								modules/migration/file_format_testdata/milestones.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
[
 | 
			
		||||
  {
 | 
			
		||||
      "title": "title_a",
 | 
			
		||||
      "description": "description_a",
 | 
			
		||||
      "deadline": "1988-04-12T23:20:50.52Z",
 | 
			
		||||
      "created": "1985-04-12T23:20:50.52Z",
 | 
			
		||||
      "updated": "1986-04-12T23:20:50.52Z",
 | 
			
		||||
      "closed": "1987-04-12T23:20:50.52Z",
 | 
			
		||||
      "state": "closed"
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
      "title": "title_b",
 | 
			
		||||
      "description": "description_b",
 | 
			
		||||
      "deadline": "1998-04-12T23:20:50.52Z",
 | 
			
		||||
      "created": "1995-04-12T23:20:50.52Z",
 | 
			
		||||
      "updated": "1996-04-12T23:20:50.52Z",
 | 
			
		||||
      "closed": null,
 | 
			
		||||
      "state": "open"
 | 
			
		||||
  }
 | 
			
		||||
]
 | 
			
		||||
@@ -28,21 +28,21 @@ func (c BasicIssueContext) ForeignID() int64 {
 | 
			
		||||
 | 
			
		||||
// Issue is a standard issue information
 | 
			
		||||
type Issue struct {
 | 
			
		||||
	Number      int64
 | 
			
		||||
	PosterID    int64  `yaml:"poster_id"`
 | 
			
		||||
	PosterName  string `yaml:"poster_name"`
 | 
			
		||||
	PosterEmail string `yaml:"poster_email"`
 | 
			
		||||
	Title       string
 | 
			
		||||
	Content     string
 | 
			
		||||
	Ref         string
 | 
			
		||||
	Milestone   string
 | 
			
		||||
	State       string // closed, open
 | 
			
		||||
	IsLocked    bool   `yaml:"is_locked"`
 | 
			
		||||
	Created     time.Time
 | 
			
		||||
	Updated     time.Time
 | 
			
		||||
	Closed      *time.Time
 | 
			
		||||
	Labels      []*Label
 | 
			
		||||
	Reactions   []*Reaction
 | 
			
		||||
	Assignees   []string
 | 
			
		||||
	Number      int64        `json:"number"`
 | 
			
		||||
	PosterID    int64        `yaml:"poster_id" json:"poster_id"`
 | 
			
		||||
	PosterName  string       `yaml:"poster_name" json:"poster_name"`
 | 
			
		||||
	PosterEmail string       `yaml:"poster_email" json:"poster_email"`
 | 
			
		||||
	Title       string       `json:"title"`
 | 
			
		||||
	Content     string       `json:"content"`
 | 
			
		||||
	Ref         string       `json:"ref"`
 | 
			
		||||
	Milestone   string       `json:"milestone"`
 | 
			
		||||
	State       string       `json:"state"` // closed, open
 | 
			
		||||
	IsLocked    bool         `yaml:"is_locked" json:"is_locked"`
 | 
			
		||||
	Created     time.Time    `json:"created"`
 | 
			
		||||
	Updated     time.Time    `json:"updated"`
 | 
			
		||||
	Closed      *time.Time   `json:"closed"`
 | 
			
		||||
	Labels      []*Label     `json:"labels"`
 | 
			
		||||
	Reactions   []*Reaction  `json:"reactions"`
 | 
			
		||||
	Assignees   []string     `json:"assignees"`
 | 
			
		||||
	Context     IssueContext `yaml:"-"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ package migration
 | 
			
		||||
 | 
			
		||||
// Label defines a standard label information
 | 
			
		||||
type Label struct {
 | 
			
		||||
	Name        string
 | 
			
		||||
	Color       string
 | 
			
		||||
	Description string
 | 
			
		||||
	Name        string `json:"name"`
 | 
			
		||||
	Color       string `json:"color"`
 | 
			
		||||
	Description string `json:"description"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,11 +9,11 @@ import "time"
 | 
			
		||||
 | 
			
		||||
// Milestone defines a standard milestone
 | 
			
		||||
type Milestone struct {
 | 
			
		||||
	Title       string
 | 
			
		||||
	Description string
 | 
			
		||||
	Deadline    *time.Time
 | 
			
		||||
	Created     time.Time
 | 
			
		||||
	Updated     *time.Time
 | 
			
		||||
	Closed      *time.Time
 | 
			
		||||
	State       string // open, closed
 | 
			
		||||
	Title       string     `json:"title"`
 | 
			
		||||
	Description string     `json:"description"`
 | 
			
		||||
	Deadline    *time.Time `json:"deadline"`
 | 
			
		||||
	Created     time.Time  `json:"created"`
 | 
			
		||||
	Updated     *time.Time `json:"updated"`
 | 
			
		||||
	Closed      *time.Time `json:"closed"`
 | 
			
		||||
	State       string     `json:"state"` // open, closed
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ package migration
 | 
			
		||||
 | 
			
		||||
// Reaction represents a reaction to an issue/pr/comment.
 | 
			
		||||
type Reaction struct {
 | 
			
		||||
	UserID   int64  `yaml:"user_id"`
 | 
			
		||||
	UserName string `yaml:"user_name"`
 | 
			
		||||
	Content  string
 | 
			
		||||
	UserID   int64  `yaml:"user_id" json:"user_id"`
 | 
			
		||||
	UserName string `yaml:"user_name" json:"user_name"`
 | 
			
		||||
	Content  string `json:"content"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										114
									
								
								modules/migration/schemas/issue.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								modules/migration/schemas/issue.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,114 @@
 | 
			
		||||
{
 | 
			
		||||
    "title": "Issue",
 | 
			
		||||
    "description": "Issues associated to a repository within a forge (Gitea, GitLab, etc.).",
 | 
			
		||||
 | 
			
		||||
    "type": "array",
 | 
			
		||||
    "items": {
 | 
			
		||||
	"type": "object",
 | 
			
		||||
	"additionalProperties": false,
 | 
			
		||||
	"properties": {
 | 
			
		||||
	    "number": {
 | 
			
		||||
		"description": "Unique identifier, relative to the repository.",
 | 
			
		||||
		"type": "number"
 | 
			
		||||
	    },
 | 
			
		||||
	    "poster_id": {
 | 
			
		||||
		"description": "Unique identifier of the user who authored the issue.",
 | 
			
		||||
		"type": "number"
 | 
			
		||||
	    },
 | 
			
		||||
	    "poster_name": {
 | 
			
		||||
		"description": "Name of the user who authored the issue.",
 | 
			
		||||
		"type": "string"
 | 
			
		||||
	    },
 | 
			
		||||
	    "poster_email": {
 | 
			
		||||
		"description": "Email of the user who authored the issue.",
 | 
			
		||||
		"type": "string"
 | 
			
		||||
	    },
 | 
			
		||||
	    "title": {
 | 
			
		||||
		"description": "Short description displayed as the title.",
 | 
			
		||||
		"type": "string"
 | 
			
		||||
	    },
 | 
			
		||||
	    "content": {
 | 
			
		||||
		"description": "Long, multiline, description.",
 | 
			
		||||
		"type": "string"
 | 
			
		||||
	    },
 | 
			
		||||
	    "ref": {
 | 
			
		||||
		"description": "Target branch in the repository.",
 | 
			
		||||
		"type": "string"
 | 
			
		||||
	    },
 | 
			
		||||
	    "milestone": {
 | 
			
		||||
		"description": "Name of the milestone.",
 | 
			
		||||
		"type": "string"
 | 
			
		||||
	    },
 | 
			
		||||
	    "state": {
 | 
			
		||||
		"description": "A 'closed' issue will not see any activity in the future, otherwise it is 'open'.",
 | 
			
		||||
		"enum": [
 | 
			
		||||
		    "closed",
 | 
			
		||||
		    "open"
 | 
			
		||||
		]
 | 
			
		||||
	    },
 | 
			
		||||
	    "is_locked": {
 | 
			
		||||
		"description": "A locked issue can only be modified by privileged users.",
 | 
			
		||||
		"type": "boolean"
 | 
			
		||||
	    },
 | 
			
		||||
	    "created": {
 | 
			
		||||
		"description": "Creation time.",
 | 
			
		||||
		"type": "string",
 | 
			
		||||
		"format": "date-time"
 | 
			
		||||
	    },
 | 
			
		||||
	    "updated": {
 | 
			
		||||
		"description": "Last update time.",
 | 
			
		||||
		"type": "string",
 | 
			
		||||
		"format": "date-time"
 | 
			
		||||
	    },
 | 
			
		||||
	    "closed": {
 | 
			
		||||
		"description": "The last time 'state' changed to 'closed'.",
 | 
			
		||||
		"anyOf": [
 | 
			
		||||
		    {
 | 
			
		||||
			"type": "string",
 | 
			
		||||
			"format": "date-time"
 | 
			
		||||
		    },
 | 
			
		||||
		    {
 | 
			
		||||
			"type": "null"
 | 
			
		||||
		    }
 | 
			
		||||
		]
 | 
			
		||||
	    },
 | 
			
		||||
	    "labels": {
 | 
			
		||||
		"description": "List of labels.",
 | 
			
		||||
		"type": "array",
 | 
			
		||||
		"items": {
 | 
			
		||||
		    "$ref": "label.json"
 | 
			
		||||
		}
 | 
			
		||||
	    },
 | 
			
		||||
	    "reactions": {
 | 
			
		||||
		"description": "List of reactions.",
 | 
			
		||||
		"type": "array",
 | 
			
		||||
		"items": {
 | 
			
		||||
		    "$ref": "reaction.json"
 | 
			
		||||
		}
 | 
			
		||||
	    },
 | 
			
		||||
	    "assignees": {
 | 
			
		||||
		"description": "List of assignees.",
 | 
			
		||||
		"type": "array",
 | 
			
		||||
		"items": {
 | 
			
		||||
		    "description": "Name of a user assigned to the issue.",
 | 
			
		||||
		    "type": "string"
 | 
			
		||||
		}
 | 
			
		||||
	    }
 | 
			
		||||
	},
 | 
			
		||||
	"required": [
 | 
			
		||||
	    "number",
 | 
			
		||||
	    "poster_id",
 | 
			
		||||
	    "poster_name",
 | 
			
		||||
	    "title",
 | 
			
		||||
	    "content",
 | 
			
		||||
	    "state",
 | 
			
		||||
	    "is_locked",
 | 
			
		||||
	    "created",
 | 
			
		||||
	    "updated"
 | 
			
		||||
	]
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    "$schema": "http://json-schema.org/draft-04/schema#",
 | 
			
		||||
    "$id": "http://example.com/issue.json",
 | 
			
		||||
    "$$target": "issue.json"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										28
									
								
								modules/migration/schemas/label.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								modules/migration/schemas/label.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
{
 | 
			
		||||
    "title": "Label",
 | 
			
		||||
    "description": "Label associated to an issue.",
 | 
			
		||||
 | 
			
		||||
    "type": "object",
 | 
			
		||||
    "additionalProperties": false,
 | 
			
		||||
    "properties": {
 | 
			
		||||
	"name": {
 | 
			
		||||
	    "description": "Name of the label, unique within the repository.",
 | 
			
		||||
	    "type": "string"
 | 
			
		||||
	},
 | 
			
		||||
	"color": {
 | 
			
		||||
	    "description": "Color code of the label.",
 | 
			
		||||
	    "type": "string"
 | 
			
		||||
	},
 | 
			
		||||
	"description": {
 | 
			
		||||
	    "description": "Long, multiline, description.",
 | 
			
		||||
	    "type": "string"
 | 
			
		||||
	}
 | 
			
		||||
    },
 | 
			
		||||
    "required": [
 | 
			
		||||
	"name"
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
    "$schema": "http://json-schema.org/draft-04/schema#",
 | 
			
		||||
    "$id": "label.json",
 | 
			
		||||
    "$$target": "label.json"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										67
									
								
								modules/migration/schemas/milestone.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								modules/migration/schemas/milestone.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,67 @@
 | 
			
		||||
{
 | 
			
		||||
    "title": "Milestone",
 | 
			
		||||
    "description": "Milestone associated to a repository within a forge.",
 | 
			
		||||
 | 
			
		||||
    "type": "array",
 | 
			
		||||
    "items": {
 | 
			
		||||
	"type": "object",
 | 
			
		||||
	"additionalProperties": false,
 | 
			
		||||
	"properties": {
 | 
			
		||||
	    "title": {
 | 
			
		||||
		"description": "Short description.",
 | 
			
		||||
		"type": "string"
 | 
			
		||||
	    },
 | 
			
		||||
	    "description": {
 | 
			
		||||
		"description": "Long, multiline, description.",
 | 
			
		||||
		"type": "string"
 | 
			
		||||
	    },
 | 
			
		||||
	    "deadline": {
 | 
			
		||||
		"description": "Deadline after which the milestone is overdue.",
 | 
			
		||||
		"type": "string",
 | 
			
		||||
		"format": "date-time"
 | 
			
		||||
	    },
 | 
			
		||||
	    "created": {
 | 
			
		||||
		"description": "Creation time.",
 | 
			
		||||
		"type": "string",
 | 
			
		||||
		"format": "date-time"
 | 
			
		||||
	    },
 | 
			
		||||
	    "updated": {
 | 
			
		||||
		"description": "Last update time.",
 | 
			
		||||
		"type": "string",
 | 
			
		||||
		"format": "date-time"
 | 
			
		||||
	    },
 | 
			
		||||
	    "closed": {
 | 
			
		||||
		"description": "The last time 'state' changed to 'closed'.",
 | 
			
		||||
		"anyOf": [
 | 
			
		||||
		    {
 | 
			
		||||
			"type": "string",
 | 
			
		||||
			"format": "date-time"
 | 
			
		||||
		    },
 | 
			
		||||
		    {
 | 
			
		||||
			"type": "null"
 | 
			
		||||
		    }
 | 
			
		||||
		]
 | 
			
		||||
	    },
 | 
			
		||||
	    "state": {
 | 
			
		||||
		"description": "A 'closed' issue will not see any activity in the future, otherwise it is 'open'.",
 | 
			
		||||
		"enum": [
 | 
			
		||||
		    "closed",
 | 
			
		||||
		    "open"
 | 
			
		||||
		]
 | 
			
		||||
	    }
 | 
			
		||||
	},
 | 
			
		||||
	"required": [
 | 
			
		||||
	    "title",
 | 
			
		||||
	    "description",
 | 
			
		||||
	    "deadline",
 | 
			
		||||
	    "created",
 | 
			
		||||
	    "updated",
 | 
			
		||||
	    "closed",
 | 
			
		||||
	    "state"
 | 
			
		||||
	]
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    "$schema": "http://json-schema.org/draft-04/schema#",
 | 
			
		||||
    "$id": "http://example.com/milestone.json",
 | 
			
		||||
    "$$target": "milestone.json"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										29
									
								
								modules/migration/schemas/reaction.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								modules/migration/schemas/reaction.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
{
 | 
			
		||||
    "title": "Reaction",
 | 
			
		||||
    "description": "Reaction associated to an issue or a comment.",
 | 
			
		||||
 | 
			
		||||
    "type": "object",
 | 
			
		||||
    "additionalProperties": false,
 | 
			
		||||
    "properties": {
 | 
			
		||||
	"user_id": {
 | 
			
		||||
	    "description": "Unique identifier of the user who authored the reaction.",
 | 
			
		||||
	    "type": "number"
 | 
			
		||||
	},
 | 
			
		||||
	"user_name": {
 | 
			
		||||
	    "description": "Name of the user who authored the reaction.",
 | 
			
		||||
	    "type": "string"
 | 
			
		||||
	},
 | 
			
		||||
	"content": {
 | 
			
		||||
	    "description": "Representation of the reaction",
 | 
			
		||||
	    "type": "string"
 | 
			
		||||
	}
 | 
			
		||||
    },
 | 
			
		||||
    "required": [
 | 
			
		||||
	"user_id",
 | 
			
		||||
	"content"
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
    "$schema": "http://json-schema.org/draft-04/schema#",
 | 
			
		||||
    "$id": "http://example.com/reaction.json",
 | 
			
		||||
    "$$target": "reaction.json"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								modules/migration/schemas_bindata.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								modules/migration/schemas_bindata.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
// Copyright 2022 The Gitea Authors. All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a MIT-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
//go:build bindata
 | 
			
		||||
// +build bindata
 | 
			
		||||
 | 
			
		||||
package migration
 | 
			
		||||
 | 
			
		||||
//go:generate go run ../../build/generate-bindata.go ../../modules/migration/schemas migration bindata.go
 | 
			
		||||
							
								
								
									
										40
									
								
								modules/migration/schemas_dynamic.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								modules/migration/schemas_dynamic.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
// Copyright 2022 The Gitea Authors. All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a MIT-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
//go:build !bindata
 | 
			
		||||
// +build !bindata
 | 
			
		||||
 | 
			
		||||
package migration
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func openSchema(s string) (io.ReadCloser, error) {
 | 
			
		||||
	u, err := url.Parse(s)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	basename := path.Base(u.Path)
 | 
			
		||||
	filename := basename
 | 
			
		||||
	//
 | 
			
		||||
	// Schema reference each other within the schemas directory but
 | 
			
		||||
	// the tests run in the parent directory.
 | 
			
		||||
	//
 | 
			
		||||
	if _, err := os.Stat(filename); os.IsNotExist(err) {
 | 
			
		||||
		filename = filepath.Join("schemas", basename)
 | 
			
		||||
		//
 | 
			
		||||
		// Integration tests run from the git root directory, not the
 | 
			
		||||
		// directory in which the test source is located.
 | 
			
		||||
		//
 | 
			
		||||
		if _, err := os.Stat(filename); os.IsNotExist(err) {
 | 
			
		||||
			filename = filepath.Join("modules/migration/schemas", basename)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return os.Open(filename)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								modules/migration/schemas_static.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								modules/migration/schemas_static.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
// Copyright 2022 The Gitea Authors. All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a MIT-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
//go:build bindata
 | 
			
		||||
// +build bindata
 | 
			
		||||
 | 
			
		||||
package migration
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io"
 | 
			
		||||
	"path"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func openSchema(filename string) (io.ReadCloser, error) {
 | 
			
		||||
	return Assets.Open(path.Base(filename))
 | 
			
		||||
}
 | 
			
		||||
@@ -17,24 +17,26 @@ import (
 | 
			
		||||
 | 
			
		||||
// RestoreParams structure holds a data for restore repository
 | 
			
		||||
type RestoreParams struct {
 | 
			
		||||
	RepoDir   string
 | 
			
		||||
	OwnerName string
 | 
			
		||||
	RepoName  string
 | 
			
		||||
	Units     []string
 | 
			
		||||
	RepoDir    string
 | 
			
		||||
	OwnerName  string
 | 
			
		||||
	RepoName   string
 | 
			
		||||
	Units      []string
 | 
			
		||||
	Validation bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RestoreRepo calls the internal RestoreRepo function
 | 
			
		||||
func RestoreRepo(ctx context.Context, repoDir, ownerName, repoName string, units []string) (int, string) {
 | 
			
		||||
func RestoreRepo(ctx context.Context, repoDir, ownerName, repoName string, units []string, validation bool) (int, string) {
 | 
			
		||||
	reqURL := setting.LocalURL + "api/internal/restore_repo"
 | 
			
		||||
 | 
			
		||||
	req := newInternalRequest(ctx, reqURL, "POST")
 | 
			
		||||
	req.SetTimeout(3*time.Second, 0) // since the request will spend much time, don't timeout
 | 
			
		||||
	req = req.Header("Content-Type", "application/json")
 | 
			
		||||
	jsonBytes, _ := json.Marshal(RestoreParams{
 | 
			
		||||
		RepoDir:   repoDir,
 | 
			
		||||
		OwnerName: ownerName,
 | 
			
		||||
		RepoName:  repoName,
 | 
			
		||||
		Units:     units,
 | 
			
		||||
		RepoDir:    repoDir,
 | 
			
		||||
		OwnerName:  ownerName,
 | 
			
		||||
		RepoName:   repoName,
 | 
			
		||||
		Units:      units,
 | 
			
		||||
		Validation: validation,
 | 
			
		||||
	})
 | 
			
		||||
	req.Body(jsonBytes)
 | 
			
		||||
	resp, err := req.Response()
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user