initial commit

This commit is contained in:
Urko. 2024-08-30 13:45:18 +02:00
commit d3a068fe2b
17 changed files with 1133 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.notes
app.yml

89
README.md Normal file
View File

@ -0,0 +1,89 @@
# gogstea
## migration from gogs to gitea with less pain
High level operation:
The main idea is to do a copy. The script will take the users from csv data from gogs, previously obtained, and will create gitea users interacting with gitea API
```go
source := gogs.mysql --> select * from user; --> csv
dst:= gitea.postgres --> API /admin gitea --> go script read from csv and http post to gitea api
copy(dst, source)
```
### users migration
Export results of this query to .csv to migrate the users
```sql
SELECT * FROM user
WHERE email !='';
```
**Execution:**
```sh
CONFIG_FILE=./app.yml go run ./cmd/users/main.go
```
### orgs migration
Export results of this query to .csv to migrate the users
```sql
SELECT
u.name,
ou.org_id,
uo.name AS owner_name,
uo.email AS owner_email
FROM org_user ou
INNER JOIN user u ON ou.org_id = u.id
INNER JOIN user uo ON uo.id = ou.is_owner
```
**Execution:**
```sh
CONFIG_FILE=./app.yml go run ./cmd/org/main.go
```
### repositories migration
You have to options: You can use the API to import them or like I did: manually import using the `unadopted` button from the UI.
**Execution:**
```sh
CONFIG_FILE=./app.yml go run ./cmd/org/main.go
```
### collaborators migration
Export results of this query to .csv to migrate the repositories
```sql
SELECT
r.name AS repo_name,
uo.name AS repo_owner,
u.name AS collaborator_user,
co.mode,
CASE
WHEN co.mode = 1 THEN 'read'
WHEN co.mode = 2 THEN 'write'
WHEN co.mode = 3 THEN 'admin'
WHEN co.mode = 4 THEN 'owner'
ELSE 'failed'
END AS permission
FROM collaboration co
INNER JOIN repository r ON r.id = co.repo_id
INNER JOIN user uo ON uo.id = r.owner_id
INNER JOIN user u ON u.id = co.user_id;
```
**Execution:**
```sh
CONFIG_FILE=./app.yml go run ./cmd/collaborator/main.go
```

12
app.example.yml Normal file
View File

@ -0,0 +1,12 @@
gitea:
url: "https://gitea.com/api/v1"
api_key: "YOUR-API-KEY-HERE"
users:
default_password: "SOME-STRONG-PASSS"
csv_path: "/home/user/gogstea/csv/user.csv"
organizations:
csv_path: "/home/user/gogstea/csv/organizations.csv"
issues:
csv_path: "/home/user/gogstea/csv/issues.csv"
collaborators:
csv_path: "/home/user/gogstea/csv/collaborators.csv"

160
cmd/clean/main.go Normal file
View File

@ -0,0 +1,160 @@
package main
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"os/signal"
"path"
"runtime"
"syscall"
"time"
"gitea.urkob.com/mcr-swiss/gogstea/kit/config"
)
type user struct {
ID int `json:"id"`
Login string `json:"login"`
LoginName string `json:"login_name"`
SourceID int `json:"source_id"`
FullName string `json:"full_name"`
Email string `json:"email"`
AvatarURL string `json:"avatar_url"`
HTMLURL string `json:"html_url"`
Language string `json:"language"`
IsAdmin bool `json:"is_admin"`
LastLogin time.Time `json:"last_login"`
Created time.Time `json:"created"`
Restricted bool `json:"restricted"`
Active bool `json:"active"`
ProhibitLogin bool `json:"prohibit_login"`
Location string `json:"location"`
Website string `json:"website"`
Description string `json:"description"`
Visibility string `json:"visibility"`
FollowersCount int `json:"followers_count"`
FollowingCount int `json:"following_count"`
StarredReposCount int `json:"starred_repos_count"`
Username string `json:"username"`
}
func main() {
ctx, cancel := context.WithCancel(signalContext(context.Background()))
defer cancel()
cfgFile := os.Getenv("CONFIG_FILE")
if cfgFile == "" {
// Get root path
_, filename, _, _ := runtime.Caller(0)
cfgFile = path.Join(path.Dir(filename), "configs", "app.yml")
}
cfg, err := config.LoadConfig(cfgFile)
if err != nil {
panic(err)
}
wd, err := os.Getwd()
if err != nil {
panic(err)
}
outputFile, err := os.Create(wd + "/output.txt")
if err != nil {
panic(err)
}
defer outputFile.Close()
cli := http.DefaultClient
parsedURL, err := url.Parse(fmt.Sprintf(cfg.Gitea.URL + "/admin/users"))
if err != nil {
panic(err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, parsedURL.String(), http.NoBody)
if err != nil {
panic(err)
}
// TODO: see how to pass the admin token if so
req.Header.Add("Authorization", "token "+cfg.Gitea.ApiKey)
req.Header.Add("Content-Type", "application/json")
resp, err := cli.Do(req)
if err != nil {
panic(err)
}
if resp.StatusCode != http.StatusOK {
bts, _ := io.ReadAll(resp.Body)
panic(fmt.Errorf("%d | %s: %w", resp.StatusCode, string(bts), err))
}
bts, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
var users []user
if err := json.Unmarshal(bts, &users); err != nil {
panic(err)
}
for _, u := range users {
if u.Username == "mcradmin" {
continue
}
// Delete
parsedURL, err := url.Parse(fmt.Sprintf("%s/admin/users/%s", cfg.Gitea.URL, u.Username))
if err != nil {
panic(err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, parsedURL.String(), http.NoBody)
if err != nil {
panic(err)
}
req.Header.Add("Authorization", "token "+cfg.Gitea.ApiKey)
req.Header.Add("Content-Type", "application/json")
resp, err := cli.Do(req)
if err != nil {
panic(err)
}
if resp.StatusCode != http.StatusOK {
bts, _ := io.ReadAll(resp.Body)
if _, err := outputFile.WriteString(fmt.Sprintf("FAILED %d | %s | %d: %s\n", u.ID, u.Email, resp.StatusCode, string(bts))); err != nil {
panic(err)
}
continue
}
if _, err := outputFile.WriteString(fmt.Sprintf("DELETED %d | %s\n", u.ID, u.Email)); err != nil {
panic(err)
}
}
}
func signalContext(ctx context.Context) context.Context {
ctx, cancel := context.WithCancel(ctx)
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigs
signal.Stop(sigs)
close(sigs)
cancel()
}()
return ctx
}

143
cmd/collaborator/main.go Normal file
View File

@ -0,0 +1,143 @@
package main
import (
"bytes"
"context"
"encoding/csv"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"os/signal"
"path"
"runtime"
"slices"
"syscall"
"gitea.urkob.com/mcr-swiss/gogstea/internal/domain"
"gitea.urkob.com/mcr-swiss/gogstea/kit/config"
)
var okStatuses = []int{
http.StatusOK,
http.StatusCreated,
http.StatusNoContent,
}
func main() {
ctx, cancel := context.WithCancel(signalContext(context.Background()))
defer cancel()
cfgFile := os.Getenv("CONFIG_FILE")
if cfgFile == "" {
// Get root path
_, filename, _, _ := runtime.Caller(0)
cfgFile = path.Join(path.Dir(filename), "configs", "app.yml")
}
cfg, err := config.LoadConfig(cfgFile)
if err != nil {
panic(err)
}
wd, err := os.Getwd()
if err != nil {
panic(err)
}
f1, err := os.Open(cfg.Collaborators.CSVPath)
if err != nil {
panic(err)
}
defer f1.Close()
r1 := csv.NewReader(f1)
r1.Comma = ',' // Set the delimiter to comma
r1.LazyQuotes = true
r1.TrimLeadingSpace = false
outputFile, err := os.Create(wd + "/collab-output.txt")
if err != nil {
panic(err)
}
defer outputFile.Close()
for {
record, err := r1.Read()
if err != nil {
if !errors.Is(err, io.EOF) {
panic(err)
}
break // Stop on EOF or other errors.
}
cli := http.DefaultClient
parsedURL, err := url.Parse(fmt.Sprintf("%s/repos/%s/%s/collaborators/%s", cfg.Gitea.URL, record[1], record[0], record[2]))
if err != nil {
panic(err)
}
// permission, err := strconv.Atoi(record[3])
// if err != nil {
// panic(err)
// }
collabReq := domain.CollaboratorCreateRequest{
Permission: record[4],
}
bts, err := json.Marshal(collabReq)
if err != nil {
panic(err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPut, parsedURL.String(), bytes.NewReader(bts))
if err != nil {
panic(err)
}
req.Header.Add("Authorization", "token "+cfg.Gitea.ApiKey)
req.Header.Add("Content-Type", "application/json")
resp, err := cli.Do(req)
if err != nil {
panic(err)
}
if !slices.Contains(okStatuses, resp.StatusCode) {
bts, _ := io.ReadAll(resp.Body)
if _, err := outputFile.WriteString(fmt.Sprintf("ERROR COLLAB | %d | %s | %s\n", resp.StatusCode, parsedURL.String(), string(bts))); err != nil {
panic(err)
}
continue
}
bts, err = io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
if _, err := outputFile.WriteString(fmt.Sprintf("OK COLLAB | %s \n", string(bts))); err != nil {
panic(err)
}
}
}
func signalContext(ctx context.Context) context.Context {
ctx, cancel := context.WithCancel(ctx)
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigs
signal.Stop(sigs)
close(sigs)
cancel()
}()
return ctx
}

130
cmd/comments/main.go Normal file
View File

@ -0,0 +1,130 @@
package main
import (
"bytes"
"context"
"encoding/csv"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"os/signal"
"path"
"runtime"
"syscall"
"gitea.urkob.com/mcr-swiss/gogstea/internal/domain"
"gitea.urkob.com/mcr-swiss/gogstea/kit/config"
)
func main() {
ctx, cancel := context.WithCancel(signalContext(context.Background()))
defer cancel()
cfgFile := os.Getenv("CONFIG_FILE")
if cfgFile == "" {
// Get root path
_, filename, _, _ := runtime.Caller(0)
cfgFile = path.Join(path.Dir(filename), "configs", "app.yml")
}
cfg, err := config.LoadConfig(cfgFile)
if err != nil {
panic(err)
}
wd, err := os.Getwd()
if err != nil {
panic(err)
}
f1, err := os.Open(cfg.Issues.CSVPath)
if err != nil {
panic(err)
}
defer f1.Close()
r1 := csv.NewReader(f1)
r1.Comma = ',' // Set the delimiter to comma
r1.LazyQuotes = true
r1.TrimLeadingSpace = false
outputFile, err := os.Create(wd + "/comments-output.txt")
if err != nil {
panic(err)
}
defer outputFile.Close()
for {
record, err := r1.Read()
if err != nil {
if !errors.Is(err, io.EOF) {
panic(err)
}
break // Stop on EOF or other errors.
}
cli := http.DefaultClient
parsedURL, err := url.Parse(fmt.Sprintf("%s/repos/{owner}/{repo}/issues", cfg.Gitea.URL, record[1], record[0]))
if err != nil {
panic(err)
}
issue := domain.CommentCreateRequest{
Body: "",
}
bts, err := json.Marshal(issue)
if err != nil {
panic(err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, parsedURL.String(), bytes.NewReader(bts))
if err != nil {
panic(err)
}
req.Header.Add("Authorization", "token "+cfg.Gitea.ApiKey)
req.Header.Add("Content-Type", "application/json")
resp, err := cli.Do(req)
if err != nil {
panic(err)
}
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
bts, _ := io.ReadAll(resp.Body)
if _, err := outputFile.WriteString(fmt.Sprintf("ERROR ISSUE | %d | %s\n", resp.StatusCode, string(bts))); err != nil {
panic(err)
}
continue
}
bts, err = io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
if _, err := outputFile.WriteString(fmt.Sprintf("OK ISSUE | %s \n", string(bts))); err != nil {
panic(err)
}
}
}
func signalContext(ctx context.Context) context.Context {
ctx, cancel := context.WithCancel(ctx)
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigs
signal.Stop(sigs)
close(sigs)
cancel()
}()
return ctx
}

156
cmd/issue/main.go Normal file
View File

@ -0,0 +1,156 @@
package main
import (
"bytes"
"context"
"encoding/csv"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"os/signal"
"path"
"runtime"
"strconv"
"syscall"
"time"
"gitea.urkob.com/mcr-swiss/gogstea/internal/domain"
"gitea.urkob.com/mcr-swiss/gogstea/kit/config"
)
func main() {
ctx, cancel := context.WithCancel(signalContext(context.Background()))
defer cancel()
cfgFile := os.Getenv("CONFIG_FILE")
if cfgFile == "" {
// Get root path
_, filename, _, _ := runtime.Caller(0)
cfgFile = path.Join(path.Dir(filename), "configs", "app.yml")
}
cfg, err := config.LoadConfig(cfgFile)
if err != nil {
panic(err)
}
wd, err := os.Getwd()
if err != nil {
panic(err)
}
f1, err := os.Open(cfg.Issues.CSVPath)
if err != nil {
panic(err)
}
defer f1.Close()
r1 := csv.NewReader(f1)
r1.Comma = ',' // Set the delimiter to comma
r1.LazyQuotes = true
r1.TrimLeadingSpace = false
outputFile, err := os.Create(wd + "/issues-output.txt")
if err != nil {
panic(err)
}
defer outputFile.Close()
for {
record, err := r1.Read()
if err != nil {
if !errors.Is(err, io.EOF) {
panic(err)
}
break // Stop on EOF or other errors.
}
cli := http.DefaultClient
parsedURL, err := url.Parse(fmt.Sprintf("%s/repos/%s/%s/issues", cfg.Gitea.URL, record[1], record[0]))
if err != nil {
panic(err)
}
isClosedInt, err := strconv.Atoi(record[7])
if err != nil {
panic(err)
}
var isClosed bool
switch isClosedInt {
case 0:
isClosed = false
case 1:
isClosed = false
default:
panic(fmt.Errorf("isclosed is %d", isClosedInt))
}
issue := domain.IssueCreateRequest{
Assignee: record[2],
Assignees: nil,
Body: record[4],
Closed: isClosed,
DueDate: time.Time{},
Labels: []int{},
Milestone: 0,
Ref: "",
Title: record[3],
}
bts, err := json.Marshal(issue)
if err != nil {
panic(err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, parsedURL.String(), bytes.NewReader(bts))
if err != nil {
panic(err)
}
req.Header.Add("Authorization", "token "+cfg.Gitea.ApiKey)
req.Header.Add("Content-Type", "application/json")
resp, err := cli.Do(req)
if err != nil {
panic(err)
}
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
bts, _ := io.ReadAll(resp.Body)
if _, err := outputFile.WriteString(fmt.Sprintf("ERROR ISSUE | %d | %s | %s\n", resp.StatusCode, parsedURL.String(), string(bts))); err != nil {
panic(err)
}
continue
}
bts, err = io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
if _, err := outputFile.WriteString(fmt.Sprintf("OK ISSUE | %s \n", string(bts))); err != nil {
panic(err)
}
}
}
func signalContext(ctx context.Context) context.Context {
ctx, cancel := context.WithCancel(ctx)
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigs
signal.Stop(sigs)
close(sigs)
cancel()
}()
return ctx
}

137
cmd/org/main.go Normal file
View File

@ -0,0 +1,137 @@
package main
import (
"bytes"
"context"
"encoding/csv"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"os/signal"
"path"
"runtime"
"syscall"
"gitea.urkob.com/mcr-swiss/gogstea/internal/domain"
"gitea.urkob.com/mcr-swiss/gogstea/kit/config"
)
func main() {
ctx, cancel := context.WithCancel(signalContext(context.Background()))
defer cancel()
cfgFile := os.Getenv("CONFIG_FILE")
if cfgFile == "" {
// Get root path
_, filename, _, _ := runtime.Caller(0)
cfgFile = path.Join(path.Dir(filename), "configs", "app.yml")
}
cfg, err := config.LoadConfig(cfgFile)
if err != nil {
panic(err)
}
wd, err := os.Getwd()
if err != nil {
panic(err)
}
f1, err := os.Open(cfg.Organizations.CSVPath)
if err != nil {
panic(err)
}
defer f1.Close()
r1 := csv.NewReader(f1)
r1.Comma = ',' // Set the delimiter to comma
r1.LazyQuotes = true
r1.TrimLeadingSpace = false
outputFile, err := os.Create(wd + "/org-output.txt")
if err != nil {
panic(err)
}
defer outputFile.Close()
for {
record, err := r1.Read()
if err != nil {
if !errors.Is(err, io.EOF) {
panic(err)
}
break // Stop on EOF or other errors.
}
cli := http.DefaultClient
parsedURL, err := url.Parse(fmt.Sprintf("%s/admin/users/%s/orgs", cfg.Gitea.URL, record[2]))
if err != nil {
panic(err)
}
bodyReq := domain.OrgCreateRequest{
Description: record[0],
Email: "",
FullName: "",
Location: "",
RepoAdminChangeTeamAccess: true,
Username: record[0],
Visibility: "private",
Website: "",
}
bts, err := json.Marshal(bodyReq)
if err != nil {
panic(err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, parsedURL.String(), bytes.NewReader(bts))
if err != nil {
panic(err)
}
req.Header.Add("Authorization", "token "+cfg.Gitea.ApiKey)
req.Header.Add("Content-Type", "application/json")
resp, err := cli.Do(req)
if err != nil {
panic(err)
}
if resp.StatusCode != http.StatusOK {
bts, _ := io.ReadAll(resp.Body)
if _, err := outputFile.WriteString(fmt.Sprintf("%s | %d: %s\n", record[3], resp.StatusCode, string(bts))); err != nil {
panic(err)
}
continue
}
bts, err = io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
if _, err := outputFile.WriteString(fmt.Sprintf("OK REPO %s | %s | OK\n", bodyReq.Username, string(bts))); err != nil {
panic(err)
}
}
}
func signalContext(ctx context.Context) context.Context {
ctx, cancel := context.WithCancel(ctx)
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigs
signal.Stop(sigs)
close(sigs)
cancel()
}()
return ctx
}

149
cmd/users/main.go Normal file
View File

@ -0,0 +1,149 @@
package main
import (
"bytes"
"context"
"encoding/csv"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"os/signal"
"path"
"runtime"
"syscall"
"gitea.urkob.com/mcr-swiss/gogstea/internal/domain"
"gitea.urkob.com/mcr-swiss/gogstea/kit/config"
)
func main() {
ctx, cancel := context.WithCancel(signalContext(context.Background()))
defer cancel()
cfgFile := os.Getenv("CONFIG_FILE")
if cfgFile == "" {
// Get root path
_, filename, _, _ := runtime.Caller(0)
cfgFile = path.Join(path.Dir(filename), "configs", "app.yml")
}
cfg, err := config.LoadConfig(cfgFile)
if err != nil {
panic(err)
}
wd, err := os.Getwd()
if err != nil {
panic(err)
}
// STEP 1 MIGRATE USERS
{
f1, err := os.Open(cfg.Users.CSVPath)
if err != nil {
panic(err)
}
defer f1.Close()
r1 := csv.NewReader(f1)
r1.Comma = ',' // Set the delimiter to comma
r1.LazyQuotes = true
r1.TrimLeadingSpace = false
outputFile, err := os.Create(wd + "/output.txt")
if err != nil {
panic(err)
}
defer outputFile.Close()
for {
record, err := r1.Read()
if err != nil {
if !errors.Is(err, io.EOF) {
panic(err)
}
break // Stop on EOF or other errors.
}
cli := http.DefaultClient
parsedURL, err := url.Parse(fmt.Sprintf(cfg.Gitea.URL + "/admin/users"))
if err != nil {
panic(err)
}
u := domain.UserCreateRequest{
Email: record[4],
FullName: record[2],
LoginName: record[1],
MustChangePassword: true,
Password: cfg.Users.DefaultPassword,
Restricted: false,
SendNotify: false,
SourceID: 0,
Username: record[1],
}
bts, err := json.Marshal(u)
if err != nil {
panic(err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, parsedURL.String(), bytes.NewReader(bts))
if err != nil {
panic(err)
}
req.Header.Add("Authorization", "token "+cfg.Gitea.ApiKey)
req.Header.Add("Content-Type", "application/json")
resp, err := cli.Do(req)
if err != nil {
panic(err)
}
if resp.StatusCode != http.StatusOK {
bts, _ := io.ReadAll(resp.Body)
if _, err := outputFile.WriteString(fmt.Sprintf("%s | %d: %s\n", u.Email, resp.StatusCode, string(bts))); err != nil {
panic(err)
}
continue
}
bts, err = io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
var uResp domain.UserGet
if err := json.Unmarshal(bts, &uResp); err != nil {
panic(err)
}
if _, err := outputFile.WriteString(fmt.Sprintf("USER %d | %s | OK\n", uResp.ID, uResp.Email)); err != nil {
panic(err)
}
}
}
}
func signalContext(ctx context.Context) context.Context {
ctx, cancel := context.WithCancel(ctx)
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigs
signal.Stop(sigs)
close(sigs)
cancel()
}()
return ctx
}

10
go.mod Normal file
View File

@ -0,0 +1,10 @@
module gitea.urkob.com/mcr-swiss/gogstea
go 1.23.0
require (
go.uber.org/zap v1.27.0
gopkg.in/yaml.v3 v3.0.1
)
require go.uber.org/multierr v1.10.0 // indirect

16
go.sum Normal file
View File

@ -0,0 +1,16 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -0,0 +1,5 @@
package domain
type CollaboratorCreateRequest struct {
Permission string `json:"permission"`
}

View File

@ -0,0 +1,5 @@
package domain
type CommentCreateRequest struct {
Body string `json:"body"`
}

15
internal/domain/issue.go Normal file
View File

@ -0,0 +1,15 @@
package domain
import "time"
type IssueCreateRequest struct {
Assignee string `json:"assignee"`
Assignees []string `json:"assignees"`
Body string `json:"body"`
Closed bool `json:"closed"`
DueDate time.Time `json:"due_date"`
Labels []int `json:"labels"`
Milestone int `json:"milestone"`
Ref string `json:"ref"`
Title string `json:"title"`
}

12
internal/domain/org.go Normal file
View File

@ -0,0 +1,12 @@
package domain
type OrgCreateRequest struct {
Description string `json:"description"`
Email string `json:"email"`
FullName string `json:"full_name"`
Location string `json:"location"`
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"`
Username string `json:"username"`
Visibility string `json:"visibility"`
Website string `json:"website"`
}

42
internal/domain/user.go Normal file
View File

@ -0,0 +1,42 @@
package domain
import "time"
type UserCreateRequest struct {
CreatedAt time.Time `json:"created_at"`
Email string `json:"email"`
FullName string `json:"full_name"`
LoginName string `json:"login_name"`
MustChangePassword bool `json:"must_change_password"`
Password string `json:"password"`
Restricted bool `json:"restricted"`
SendNotify bool `json:"send_notify"`
SourceID int `json:"source_id"`
Username string `json:"username"`
}
type UserGet struct {
ID int `json:"id"`
Login string `json:"login"`
LoginName string `json:"login_name"`
SourceID int `json:"source_id"`
FullName string `json:"full_name"`
Email string `json:"email"`
AvatarURL string `json:"avatar_url"`
HTMLURL string `json:"html_url"`
Language string `json:"language"`
IsAdmin bool `json:"is_admin"`
LastLogin time.Time `json:"last_login"`
Created time.Time `json:"created"`
Restricted bool `json:"restricted"`
Active bool `json:"active"`
ProhibitLogin bool `json:"prohibit_login"`
Location string `json:"location"`
Website string `json:"website"`
Description string `json:"description"`
Visibility string `json:"visibility"`
FollowersCount int `json:"followers_count"`
FollowingCount int `json:"following_count"`
StarredReposCount int `json:"starred_repos_count"`
Username string `json:"username"`
}

50
kit/config/config.go Normal file
View File

@ -0,0 +1,50 @@
package config
import (
"os"
"go.uber.org/zap"
"gopkg.in/yaml.v3"
)
type Config struct {
Gitea struct {
URL string `yaml:"url"`
ApiKey string `yaml:"api_key"`
} `yaml:"gitea"`
Users struct {
DefaultPassword string `yaml:"default_password"`
CSVPath string `yaml:"csv_path"`
} `yaml:"users"`
Organizations struct {
CSVPath string `yaml:"csv_path"`
} `yaml:"organizations"`
Issues struct {
CSVPath string `yaml:"csv_path"`
} `yaml:"issues"`
Collaborators struct {
CSVPath string `yaml:"csv_path"`
} `yaml:"collaborators"`
}
func LoadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var config Config
if err := yaml.Unmarshal(data, &config); err != nil {
return nil, err
}
return &config, nil
}
func NewZapCfg(dev bool) zap.Config {
prodConfig := zap.NewProductionConfig()
if dev {
prodConfig = zap.NewDevelopmentConfig()
}
return prodConfig
}