├── internal ├── usecase │ ├── cursor.go │ ├── interactor │ │ ├── emails │ │ │ └── auth_text.tmpl │ │ ├── team_test.go │ │ ├── published_test.go │ │ └── layer_test.go │ ├── gateway │ │ ├── google.go │ │ ├── mailer.go │ │ ├── container.go │ │ ├── authenticator.go │ │ ├── plugin_registry.go │ │ ├── datasouce.go │ │ └── file.go │ ├── repo │ │ ├── config.go │ │ ├── lock.go │ │ ├── scene_lock.go │ │ ├── auth_request.go │ │ ├── transaction.go │ │ ├── team.go │ │ ├── scene.go │ │ ├── plugin.go │ │ ├── asset.go │ │ ├── user.go │ │ ├── property_schema.go │ │ ├── dataset_schema.go │ │ ├── property.go │ │ ├── project.go │ │ └── tag.go │ ├── pagination.go │ ├── interfaces │ │ ├── common.go │ │ ├── plugin.go │ │ ├── asset.go │ │ ├── team.go │ │ └── published.go │ └── pageinfo.go ├── infrastructure │ ├── marketplace │ │ └── testdata │ │ │ └── test.zip │ ├── fs │ │ ├── common.go │ │ └── plugin_test.go │ ├── memory │ │ ├── config_test.go │ │ ├── lock.go │ │ ├── container.go │ │ ├── layer_test.go │ │ ├── transaction_test.go │ │ ├── config.go │ │ └── transaction.go │ ├── google │ │ ├── google.go │ │ └── fetch.go │ ├── mailer │ │ ├── logger.go │ │ ├── mock.go │ │ ├── sendgrid.go │ │ └── smtp.go │ ├── mongo │ │ ├── transaction.go │ │ ├── migration │ │ │ ├── migrations.go │ │ │ ├── 210310145844_remove_preview_token.go │ │ │ ├── 220309174648_add_scene_field_to_property_schema.go │ │ │ ├── 201217132559_add_scene_widget_id.go │ │ │ └── 220214180713_split_schema_of_properties.go │ │ ├── mongodoc │ │ │ ├── config.go │ │ │ ├── consumer.go │ │ │ ├── pagination.go │ │ │ └── consumer_test.go │ │ └── mongo_test.go │ └── gcs │ │ └── file_test.go ├── adapter │ ├── gql │ │ ├── resolver_mutation.go │ │ ├── gqlmodel │ │ │ ├── convert_value_test.go │ │ │ ├── convert_asset.go │ │ │ ├── convert_property_test.go │ │ │ └── convert_tag.go │ │ ├── resolver.go │ │ ├── resolver_asset.go │ │ ├── resolver_user.go │ │ ├── resolver_project.go │ │ ├── resolver_property_test.go │ │ ├── resolver_mutation_asset.go │ │ ├── resolver_team.go │ │ └── context.go │ └── http │ │ └── published.go └── app │ ├── profiler.go │ ├── private.go │ ├── web.go │ └── usecase.go ├── main.go ├── pkg ├── plugin │ ├── pluginpack │ │ ├── testdata │ │ │ ├── test │ │ │ │ ├── test │ │ │ │ │ └── foo.bar │ │ │ │ ├── index.js │ │ │ │ ├── reearth_ja.yml │ │ │ │ ├── reearth_zh-CN.yml │ │ │ │ └── reearth.yml │ │ │ └── test.zip │ │ └── package_test.go │ ├── manifest │ │ ├── testdata │ │ │ ├── minimum.yml │ │ │ ├── test.yml │ │ │ └── translation.yml │ │ ├── manifest.go │ │ └── parser_translation.go │ ├── loader.go │ ├── metadata.go │ ├── id.go │ ├── list.go │ └── list_test.go ├── layer │ ├── item_test.go │ ├── layer_test.go │ ├── decoding │ │ ├── shapetest │ │ │ ├── point.shp │ │ │ ├── shapes.zip │ │ │ ├── polygon.shp │ │ │ └── polyline.shp │ │ ├── format.go │ │ └── decoder.go │ ├── item_builder_test.go │ ├── group_builder_test.go │ ├── encoding │ │ ├── common_test.go │ │ ├── encoder.go │ │ ├── common.go │ │ └── exporter.go │ ├── layerops │ │ └── processor.go │ ├── infobox_field_builder.go │ ├── infobox_field.go │ └── builder.go ├── builtin │ └── migration.go ├── file │ ├── testdata │ │ ├── test.zip │ │ └── test.tar.gz │ ├── targz.go │ └── zip_test.go ├── shp │ ├── test_files │ │ ├── empty.zip │ │ ├── multi.zip │ │ ├── point.shp │ │ ├── pointm.shp │ │ ├── pointz.shp │ │ ├── polygon.shp │ │ ├── polygonm.shp │ │ ├── polygonz.shp │ │ ├── polyline.shp │ │ ├── polylinem.shp │ │ ├── polylinez.shp │ │ ├── multipatch.shp │ │ ├── multipoint.shp │ │ ├── multipointm.shp │ │ ├── multipointz.shp │ │ └── ne_110m_admin_0_countries.zip │ ├── errreader.go │ ├── sequentialreader_test.go │ └── shapetype_string.go ├── dataset │ ├── diff.go │ ├── loader.go │ ├── schema_field_diff.go │ ├── graph_loader.go │ ├── field.go │ ├── schema_field.go │ ├── schema_list_test.go │ ├── schema_list.go │ ├── id.go │ └── schema_field_builder.go ├── id │ ├── common.go │ └── idx │ │ ├── id_test.go │ │ ├── string.go │ │ ├── string_test.go │ │ ├── ulid.go │ │ └── set.go ├── visualizer │ └── visualizer.go ├── util │ ├── now.go │ ├── util.go │ └── util_test.go ├── property │ ├── id_test.go │ ├── migrator.go │ ├── condition.go │ ├── schema_pointer.go │ ├── schema_field_ui_test.go │ ├── validator.go │ ├── item_builder.go │ ├── schema_pointer_test.go │ ├── condition_test.go │ ├── value_camera_test.go │ ├── field_builder.go │ ├── loader.go │ ├── builder.go │ ├── schema_builder.go │ ├── value_dataset.go │ └── item.go ├── user │ ├── theme.go │ ├── team.go │ ├── id.go │ ├── auth.go │ ├── team_test.go │ ├── password_reset.go │ ├── role.go │ ├── team_builder.go │ ├── initializer.go │ ├── team_list.go │ └── password_test.go ├── project │ ├── publishment_status.go │ └── id.go ├── config │ ├── config_test.go │ └── config.go ├── tag │ ├── group.go │ ├── tag_test.go │ ├── map.go │ ├── tag.go │ ├── item.go │ ├── loader.go │ ├── id.go │ └── group_builder.go ├── asset │ ├── id.go │ ├── sort_type.go │ ├── asset.go │ └── builder.go ├── scene │ ├── plugin_test.go │ ├── plugin.go │ ├── list.go │ ├── cluster.go │ ├── lock.go │ └── list_test.go ├── value │ ├── latlng_test.go │ ├── latlngheight_test.go │ ├── ref.go │ ├── bool.go │ ├── url.go │ ├── rect.go │ ├── string.go │ ├── string_test.go │ ├── polygon.go │ ├── latlng.go │ ├── type.go │ ├── bool_test.go │ ├── latlngheight.go │ └── optional.go ├── cache │ └── cache.go ├── i18n │ └── string.go ├── writer │ └── seeker_closer.go └── czml │ └── czml.go ├── .graphqlconfig ├── cmd └── reearth │ ├── debug.go │ ├── release.go │ └── main.go ├── .github ├── CODEOWNERS ├── pull_request_template.md ├── changelog.yml ├── workflows │ ├── renovate.yml │ ├── stage.yml │ └── pr_title.yml └── renovate.json ├── tools.go ├── .golangci.yml ├── codecov.yml ├── Makefile ├── .vscode ├── launch.json ├── extensions.json └── settings.json ├── README.md ├── .gitignore ├── docker-compose.yml ├── Dockerfile ├── .goreleaser.yml ├── tools └── cmd │ └── shapefiletest │ └── main.go └── gqlgen.yml /internal/usecase/cursor.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() {} 4 | -------------------------------------------------------------------------------- /pkg/plugin/pluginpack/testdata/test/test/foo.bar: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /.graphqlconfig: -------------------------------------------------------------------------------- 1 | { 2 | "schemaPath": "schema.graphql" 3 | } 4 | -------------------------------------------------------------------------------- /pkg/layer/item_test.go: -------------------------------------------------------------------------------- 1 | package layer 2 | 3 | var _ Layer = &Item{} 4 | -------------------------------------------------------------------------------- /pkg/plugin/manifest/testdata/minimum.yml: -------------------------------------------------------------------------------- 1 | id: aaa 2 | version: 1.1.1 3 | -------------------------------------------------------------------------------- /pkg/builtin/migration.go: -------------------------------------------------------------------------------- 1 | package builtin 2 | 3 | // TODO: migration code 4 | -------------------------------------------------------------------------------- /pkg/plugin/pluginpack/testdata/test/index.js: -------------------------------------------------------------------------------- 1 | console.log("hello world"); 2 | -------------------------------------------------------------------------------- /pkg/plugin/pluginpack/testdata/test/reearth_ja.yml: -------------------------------------------------------------------------------- 1 | { "name": "テストプラグイン" } 2 | -------------------------------------------------------------------------------- /pkg/plugin/pluginpack/testdata/test/reearth_zh-CN.yml: -------------------------------------------------------------------------------- 1 | { "name": "测试插件" } 2 | -------------------------------------------------------------------------------- /cmd/reearth/debug.go: -------------------------------------------------------------------------------- 1 | //go:build !release 2 | 3 | package main 4 | 5 | const debug = true 6 | -------------------------------------------------------------------------------- /cmd/reearth/release.go: -------------------------------------------------------------------------------- 1 | //go:build release 2 | 3 | package main 4 | 5 | const debug = false 6 | -------------------------------------------------------------------------------- /pkg/layer/layer_test.go: -------------------------------------------------------------------------------- 1 | package layer 2 | 3 | var _ Layer = &Item{} 4 | var _ Layer = &Group{} 5 | -------------------------------------------------------------------------------- /pkg/file/testdata/test.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/reearth-backend/HEAD/pkg/file/testdata/test.zip -------------------------------------------------------------------------------- /pkg/file/testdata/test.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/reearth-backend/HEAD/pkg/file/testdata/test.tar.gz -------------------------------------------------------------------------------- /pkg/plugin/pluginpack/testdata/test/reearth.yml: -------------------------------------------------------------------------------- 1 | { "id": "testplugin", "version": "1.0.1", "name": "testplugin" } 2 | -------------------------------------------------------------------------------- /pkg/shp/test_files/empty.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/reearth-backend/HEAD/pkg/shp/test_files/empty.zip -------------------------------------------------------------------------------- /pkg/shp/test_files/multi.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/reearth-backend/HEAD/pkg/shp/test_files/multi.zip -------------------------------------------------------------------------------- /pkg/shp/test_files/point.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/reearth-backend/HEAD/pkg/shp/test_files/point.shp -------------------------------------------------------------------------------- /pkg/shp/test_files/pointm.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/reearth-backend/HEAD/pkg/shp/test_files/pointm.shp -------------------------------------------------------------------------------- /pkg/shp/test_files/pointz.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/reearth-backend/HEAD/pkg/shp/test_files/pointz.shp -------------------------------------------------------------------------------- /pkg/shp/test_files/polygon.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/reearth-backend/HEAD/pkg/shp/test_files/polygon.shp -------------------------------------------------------------------------------- /pkg/shp/test_files/polygonm.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/reearth-backend/HEAD/pkg/shp/test_files/polygonm.shp -------------------------------------------------------------------------------- /pkg/shp/test_files/polygonz.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/reearth-backend/HEAD/pkg/shp/test_files/polygonz.shp -------------------------------------------------------------------------------- /pkg/shp/test_files/polyline.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/reearth-backend/HEAD/pkg/shp/test_files/polyline.shp -------------------------------------------------------------------------------- /pkg/shp/test_files/polylinem.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/reearth-backend/HEAD/pkg/shp/test_files/polylinem.shp -------------------------------------------------------------------------------- /pkg/shp/test_files/polylinez.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/reearth-backend/HEAD/pkg/shp/test_files/polylinez.shp -------------------------------------------------------------------------------- /pkg/shp/test_files/multipatch.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/reearth-backend/HEAD/pkg/shp/test_files/multipatch.shp -------------------------------------------------------------------------------- /pkg/shp/test_files/multipoint.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/reearth-backend/HEAD/pkg/shp/test_files/multipoint.shp -------------------------------------------------------------------------------- /pkg/shp/test_files/multipointm.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/reearth-backend/HEAD/pkg/shp/test_files/multipointm.shp -------------------------------------------------------------------------------- /pkg/shp/test_files/multipointz.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/reearth-backend/HEAD/pkg/shp/test_files/multipointz.shp -------------------------------------------------------------------------------- /pkg/dataset/diff.go: -------------------------------------------------------------------------------- 1 | package dataset 2 | 3 | type Diff struct { 4 | Added List 5 | Removed List 6 | Others map[ID]*Dataset 7 | } 8 | -------------------------------------------------------------------------------- /pkg/layer/decoding/shapetest/point.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/reearth-backend/HEAD/pkg/layer/decoding/shapetest/point.shp -------------------------------------------------------------------------------- /pkg/layer/decoding/shapetest/shapes.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/reearth-backend/HEAD/pkg/layer/decoding/shapetest/shapes.zip -------------------------------------------------------------------------------- /pkg/plugin/pluginpack/testdata/test.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/reearth-backend/HEAD/pkg/plugin/pluginpack/testdata/test.zip -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @rot1024 2 | /pkg/builtin/manifest.yml @HideBa 3 | /pkg/builtin/manifest_ja.yml @HideBa 4 | -------------------------------------------------------------------------------- /pkg/id/common.go: -------------------------------------------------------------------------------- 1 | package id 2 | 3 | import "github.com/reearth/reearth-backend/pkg/id/idx" 4 | 5 | var ErrInvalidID = idx.ErrInvalidID 6 | -------------------------------------------------------------------------------- /pkg/layer/decoding/shapetest/polygon.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/reearth-backend/HEAD/pkg/layer/decoding/shapetest/polygon.shp -------------------------------------------------------------------------------- /pkg/layer/decoding/shapetest/polyline.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/reearth-backend/HEAD/pkg/layer/decoding/shapetest/polyline.shp -------------------------------------------------------------------------------- /pkg/plugin/loader.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type Loader func(context.Context, []ID) ([]*Plugin, error) 8 | -------------------------------------------------------------------------------- /pkg/visualizer/visualizer.go: -------------------------------------------------------------------------------- 1 | package visualizer 2 | 3 | type Visualizer string 4 | 5 | const ( 6 | VisualizerCesium Visualizer = "cesium" 7 | ) 8 | -------------------------------------------------------------------------------- /pkg/shp/test_files/ne_110m_admin_0_countries.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/reearth-backend/HEAD/pkg/shp/test_files/ne_110m_admin_0_countries.zip -------------------------------------------------------------------------------- /internal/usecase/interactor/emails/auth_text.tmpl: -------------------------------------------------------------------------------- 1 | Hi {{ .UserName }}: 2 | {{ .Message }} 3 | 4 | To {{ .ActionLabel }}: 5 | {{ .ActionURL }} 6 | 7 | {{ .Suffix }} -------------------------------------------------------------------------------- /internal/infrastructure/marketplace/testdata/test.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/reearth-backend/HEAD/internal/infrastructure/marketplace/testdata/test.zip -------------------------------------------------------------------------------- /cmd/reearth/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/reearth/reearth-backend/internal/app" 4 | 5 | var version = "" 6 | 7 | func main() { 8 | app.Start(debug, version) 9 | } 10 | -------------------------------------------------------------------------------- /tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | 3 | package main 4 | 5 | import ( 6 | _ "github.com/99designs/gqlgen" 7 | _ "github.com/idubinskiy/schematyper" 8 | _ "github.com/vektah/dataloaden" 9 | ) 10 | -------------------------------------------------------------------------------- /internal/adapter/gql/resolver_mutation.go: -------------------------------------------------------------------------------- 1 | package gql 2 | 3 | func (r *Resolver) Mutation() MutationResolver { 4 | return &mutationResolver{r} 5 | } 6 | 7 | type mutationResolver struct{ *Resolver } 8 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | issues: 2 | exclude-use-default: false 3 | 4 | linters: 5 | enable: 6 | - gofmt 7 | - goimports 8 | 9 | goimports: 10 | local-prefixes: github.com/reearth/reearth-backend 11 | -------------------------------------------------------------------------------- /internal/infrastructure/fs/common.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | const ( 4 | assetDir = "assets" 5 | pluginDir = "plugins" 6 | publishedDir = "published" 7 | manifestFilePath = "reearth.yml" 8 | ) 9 | -------------------------------------------------------------------------------- /internal/usecase/gateway/google.go: -------------------------------------------------------------------------------- 1 | package gateway 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | type Google interface { 8 | FetchCSV(token string, fileId string, sheetName string) (*io.ReadCloser, error) 9 | } 10 | -------------------------------------------------------------------------------- /pkg/util/now.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "time" 4 | 5 | var Now = time.Now 6 | 7 | func MockNow(t time.Time) func() { 8 | Now = func() time.Time { return t } 9 | return func() { Now = time.Now } 10 | } 11 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | ## What I've done 4 | 5 | 6 | ## What I haven't done 7 | 8 | ## How I tested 9 | 10 | ## Which point I want you to review particularly 11 | 12 | ## Memo 13 | -------------------------------------------------------------------------------- /pkg/property/id_test.go: -------------------------------------------------------------------------------- 1 | package property 2 | 3 | func mockNewItemID(id ItemID) func() { 4 | original := NewItemID 5 | NewItemID = func() ItemID { return id } 6 | return func() { 7 | NewItemID = original 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /internal/usecase/gateway/mailer.go: -------------------------------------------------------------------------------- 1 | package gateway 2 | 3 | type Contact struct { 4 | Email string 5 | Name string 6 | } 7 | 8 | type Mailer interface { 9 | SendMail(toContacts []Contact, subject, plainContent, htmlContent string) error 10 | } 11 | -------------------------------------------------------------------------------- /pkg/user/theme.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | type Theme string 4 | 5 | const ( 6 | ThemeDefault Theme = "default" 7 | ThemeLight Theme = "light" 8 | ThemeDark Theme = "dark" 9 | ) 10 | 11 | func (t Theme) Ref() *Theme { 12 | return &t 13 | } 14 | -------------------------------------------------------------------------------- /internal/usecase/gateway/container.go: -------------------------------------------------------------------------------- 1 | package gateway 2 | 3 | type Container struct { 4 | Authenticator Authenticator 5 | Mailer Mailer 6 | DataSource DataSource 7 | PluginRegistry PluginRegistry 8 | File File 9 | Google Google 10 | } 11 | -------------------------------------------------------------------------------- /pkg/layer/item_builder_test.go: -------------------------------------------------------------------------------- 1 | package layer 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestItemBuilder_Tags(t *testing.T) { 10 | l := NewTagList(nil) 11 | b := NewItem().NewID().Tags(l).MustBuild() 12 | assert.Same(t, l, b.Tags()) 13 | } 14 | -------------------------------------------------------------------------------- /pkg/layer/group_builder_test.go: -------------------------------------------------------------------------------- 1 | package layer 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestGroupBuilder_Tags(t *testing.T) { 10 | l := NewTagList(nil) 11 | b := NewGroup().NewID().Tags(l).MustBuild() 12 | assert.Same(t, l, b.Tags()) 13 | } 14 | -------------------------------------------------------------------------------- /pkg/project/publishment_status.go: -------------------------------------------------------------------------------- 1 | package project 2 | 3 | type PublishmentStatus string 4 | 5 | const ( 6 | PublishmentStatusPublic PublishmentStatus = "public" 7 | 8 | PublishmentStatusLimited PublishmentStatus = "limited" 9 | 10 | PublishmentStatusPrivate PublishmentStatus = "private" 11 | ) 12 | -------------------------------------------------------------------------------- /pkg/property/migrator.go: -------------------------------------------------------------------------------- 1 | package property 2 | 3 | type Migrator struct { 4 | NewSchema *Schema 5 | Plans []MigrationPlan 6 | } 7 | 8 | type MigrationPlan struct { 9 | From *Pointer 10 | To *Pointer 11 | } 12 | 13 | // func (m Migrator) Migrate(from *Property) *Property { 14 | 15 | // } 16 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: 2 | layout: 'reach, diff, flags, files' 3 | behavior: default 4 | require_changes: false 5 | ignore: 6 | - "**/*_gen.go" 7 | - "**/*_test.go" 8 | - "**/doc.go" 9 | - "**/testdata" 10 | - internal/adapter/gql/generated.go 11 | - tools 12 | - main.go 13 | - tools.go 14 | -------------------------------------------------------------------------------- /internal/infrastructure/memory/config_test.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestConfig(t *testing.T) { 11 | ctx := context.Background() 12 | c := NewConfig() 13 | assert.NoError(t, c.Unlock(ctx)) 14 | } 15 | -------------------------------------------------------------------------------- /pkg/property/condition.go: -------------------------------------------------------------------------------- 1 | package property 2 | 3 | type Condition struct { 4 | Field FieldID 5 | Value *Value 6 | } 7 | 8 | func (c *Condition) Clone() *Condition { 9 | if c == nil { 10 | return nil 11 | } 12 | return &Condition{ 13 | Field: c.Field, 14 | Value: c.Value.Clone(), 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /pkg/config/config_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestConfigNextMigrations(t *testing.T) { 10 | c := &Config{ 11 | Migration: 100, 12 | } 13 | assert.Equal(t, []int64{200, 500}, c.NextMigrations([]int64{1, 100, 500, 200, 2})) 14 | } 15 | -------------------------------------------------------------------------------- /pkg/plugin/metadata.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import "time" 4 | 5 | type Metadata struct { 6 | Name string `json:"name"` 7 | Description string `json:"description"` 8 | ThumbnailUrl string `json:"thumbnailUrl"` 9 | Author string `json:"author"` 10 | CreatedAt time.Time `json:"createdAt"` 11 | } 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | lint: 2 | golangci-lint run --fix 3 | 4 | test: 5 | go test -race -v ./... 6 | 7 | build: 8 | go build ./cmd/reearth 9 | 10 | run-app: 11 | go run ./cmd/reearth 12 | 13 | run-db: 14 | docker compose up -d reearth-mongo 15 | 16 | gql: 17 | go generate ./internal/adapter/gql 18 | 19 | .PHONY: lint test build run-app run-db gql 20 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch", 6 | "type": "go", 7 | "request": "launch", 8 | "mode": "auto", 9 | "cwd": "${workspaceRoot}", 10 | "program": "${workspaceRoot}/cmd/reearth", 11 | "env": {}, 12 | "args": [] 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /internal/usecase/repo/config.go: -------------------------------------------------------------------------------- 1 | package repo 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/reearth/reearth-backend/pkg/config" 7 | ) 8 | 9 | type Config interface { 10 | LockAndLoad(context.Context) (*config.Config, error) 11 | Save(context.Context, *config.Config) error 12 | SaveAndUnlock(context.Context, *config.Config) error 13 | Unlock(context.Context) error 14 | } 15 | -------------------------------------------------------------------------------- /internal/usecase/repo/lock.go: -------------------------------------------------------------------------------- 1 | package repo 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | ) 7 | 8 | var ( 9 | ErrFailedToLock = errors.New("failed to lock") 10 | ErrAlreadyLocked = errors.New("already locked") 11 | ErrNotLocked = errors.New("not locked") 12 | ) 13 | 14 | type Lock interface { 15 | Lock(context.Context, string) error 16 | Unlock(context.Context, string) error 17 | } 18 | -------------------------------------------------------------------------------- /.github/changelog.yml: -------------------------------------------------------------------------------- 1 | prefixes: 2 | feat: 🚀 Features 3 | fix: 🔧 Bug Fixes 4 | docs: 📖 Documentation 5 | doc: 📖 Documentation 6 | perf: ⚡️ Performance 7 | refactor: ✨ Refactor 8 | style: 🎨 Styling 9 | test: 🧪 Testing 10 | chore: Miscellaneous Tasks 11 | build: Miscellaneous Tasks 12 | deps: Miscellaneous Tasks 13 | ci: false 14 | revert: false 15 | titleVersionPrefix: remove 16 | -------------------------------------------------------------------------------- /internal/adapter/gql/gqlmodel/convert_value_test.go: -------------------------------------------------------------------------------- 1 | package gqlmodel 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/reearth/reearth-backend/pkg/value" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func Test_FromValueType(t *testing.T) { 11 | assert.Equal(t, value.TypeString, FromValueType(ValueTypeString)) 12 | assert.Equal(t, value.TypeNumber, FromValueType(ValueTypeNumber)) 13 | } 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # reearth-backend (DEPRECATED AND MIGRATED TO [reearth/reearth](https://github.com/reearth/reearth/blob/main/server)) 2 | 3 | **⚠️NOTE: This repository has been migrated to [reearth/reearth](https://github.com/reearth/reearth/blob/main/server) repository and this repo will be no longer used.** 4 | 5 | ## License 6 | 7 | Distributed under the Apache-2.0 License. See [Apache License 2.0](LICENSE) for more information. 8 | -------------------------------------------------------------------------------- /pkg/layer/decoding/format.go: -------------------------------------------------------------------------------- 1 | package decoding 2 | 3 | type LayerEncodingFormat string 4 | 5 | const ( 6 | LayerEncodingFormatKML LayerEncodingFormat = "kml" 7 | LayerEncodingFormatCZML LayerEncodingFormat = "czml" 8 | LayerEncodingFormatGEOJSON LayerEncodingFormat = "geojson" 9 | LayerEncodingFormatSHAPE LayerEncodingFormat = "shape" 10 | LayerEncodingFormatREEARTH LayerEncodingFormat = "reearth" 11 | ) 12 | -------------------------------------------------------------------------------- /pkg/property/schema_pointer.go: -------------------------------------------------------------------------------- 1 | package property 2 | 3 | type SchemaFieldPointer struct { 4 | SchemaGroup SchemaGroupID 5 | Field FieldID 6 | } 7 | 8 | func (p SchemaFieldPointer) Pointer() *Pointer { 9 | return PointFieldBySchemaGroup(p.SchemaGroup, p.Field) 10 | } 11 | 12 | func (p *SchemaFieldPointer) Clone() *SchemaFieldPointer { 13 | if p == nil { 14 | return p 15 | } 16 | p2 := *p 17 | return &p2 18 | } 19 | -------------------------------------------------------------------------------- /pkg/util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | func Must[T any](v T, err error) T { 4 | if err != nil { 5 | panic(err) 6 | } 7 | return v 8 | } 9 | 10 | func IsZero[T comparable](v T) bool { 11 | var z T 12 | return v == z 13 | } 14 | 15 | func IsNotZero[T comparable](v T) bool { 16 | return !IsZero(v) 17 | } 18 | 19 | func Deref[T any](r *T) T { 20 | if r == nil { 21 | var z T 22 | return z 23 | } 24 | return *r 25 | } 26 | -------------------------------------------------------------------------------- /internal/infrastructure/google/google.go: -------------------------------------------------------------------------------- 1 | package google 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/reearth/reearth-backend/internal/usecase/gateway" 7 | ) 8 | 9 | type google struct { 10 | } 11 | 12 | func NewGoogle() gateway.Google { 13 | return &google{} 14 | } 15 | 16 | func (g google) FetchCSV(token string, fileId string, sheetName string) (*io.ReadCloser, error) { 17 | return fetchCSV(token, fileId, sheetName) 18 | } 19 | -------------------------------------------------------------------------------- /internal/infrastructure/memory/lock.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/reearth/reearth-backend/internal/usecase/repo" 7 | ) 8 | 9 | type Lock struct{} 10 | 11 | func NewLock() repo.Lock { 12 | return &Lock{} 13 | } 14 | 15 | func (r *Lock) Lock(_ context.Context, _ string) error { 16 | return nil 17 | } 18 | 19 | func (r *Lock) Unlock(_ context.Context, _ string) error { 20 | return nil 21 | } 22 | -------------------------------------------------------------------------------- /internal/adapter/gql/resolver.go: -------------------------------------------------------------------------------- 1 | //go:generate go run github.com/99designs/gqlgen 2 | 3 | package gql 4 | 5 | import ( 6 | "errors" 7 | ) 8 | 9 | // THIS CODE IS A STARTING POINT ONLY. IT WILL NOT BE UPDATED WITH SCHEMA CHANGES. 10 | 11 | var ErrNotImplemented = errors.New("not impleneted yet") 12 | var ErrUnauthorized = errors.New("unauthorized") 13 | 14 | type Resolver struct { 15 | } 16 | 17 | func NewResolver() ResolverRoot { 18 | return &Resolver{} 19 | } 20 | -------------------------------------------------------------------------------- /internal/adapter/gql/resolver_asset.go: -------------------------------------------------------------------------------- 1 | package gql 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/reearth/reearth-backend/internal/adapter/gql/gqlmodel" 7 | ) 8 | 9 | func (r *Resolver) Asset() AssetResolver { 10 | return &assetResolver{r} 11 | } 12 | 13 | type assetResolver struct{ *Resolver } 14 | 15 | func (r *assetResolver) Team(ctx context.Context, obj *gqlmodel.Asset) (*gqlmodel.Team, error) { 16 | return dataloaders(ctx).Team.Load(obj.TeamID) 17 | } 18 | -------------------------------------------------------------------------------- /pkg/tag/group.go: -------------------------------------------------------------------------------- 1 | package tag 2 | 3 | type Group struct { 4 | tag 5 | tags IDList 6 | } 7 | 8 | func (g *Group) Tags() IDList { 9 | if g == nil { 10 | return nil 11 | } 12 | return g.tags.Clone() 13 | } 14 | 15 | func (g *Group) RemoveTag(ids ...ID) { 16 | if g == nil { 17 | return 18 | } 19 | g.tags = g.tags.Delete(ids...) 20 | } 21 | 22 | func (g *Group) AddTag(ids ...ID) { 23 | if g == nil { 24 | return 25 | } 26 | g.tags = g.tags.Add(ids...) 27 | } 28 | -------------------------------------------------------------------------------- /internal/usecase/gateway/authenticator.go: -------------------------------------------------------------------------------- 1 | package gateway 2 | 3 | type AuthenticatorUpdateUserParam struct { 4 | ID string 5 | Name *string 6 | Email *string 7 | Password *string 8 | } 9 | 10 | type AuthenticatorUser struct { 11 | ID string 12 | Name string 13 | Email string 14 | EmailVerified bool 15 | } 16 | 17 | type Authenticator interface { 18 | UpdateUser(AuthenticatorUpdateUserParam) (AuthenticatorUser, error) 19 | } 20 | -------------------------------------------------------------------------------- /internal/usecase/gateway/plugin_registry.go: -------------------------------------------------------------------------------- 1 | package gateway 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | "github.com/reearth/reearth-backend/pkg/id" 8 | "github.com/reearth/reearth-backend/pkg/plugin/pluginpack" 9 | ) 10 | 11 | var ErrFailedToFetchDataFromPluginRegistry = errors.New("failed to fetch data from the plugin registry") 12 | 13 | type PluginRegistry interface { 14 | FetchPluginPackage(context.Context, id.PluginID) (*pluginpack.Package, error) 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | .DS_Store 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # Editor 16 | .idea 17 | 18 | # reearth 19 | /dist 20 | /reearth 21 | /reearth-backend 22 | __debug_bin 23 | /data 24 | /bin 25 | /debug 26 | /mongo 27 | /.env* 28 | !/.env.example 29 | /coverage.txt 30 | /web 31 | -------------------------------------------------------------------------------- /internal/usecase/gateway/datasouce.go: -------------------------------------------------------------------------------- 1 | package gateway 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | "github.com/reearth/reearth-backend/pkg/dataset" 8 | "github.com/reearth/reearth-backend/pkg/id" 9 | ) 10 | 11 | var ( 12 | ErrDataSourceInvalidURL error = errors.New("invalid url") 13 | ) 14 | 15 | type DataSource interface { 16 | Fetch(context.Context, string, id.SceneID) ([]*dataset.Schema, []*dataset.Dataset, error) 17 | IsURLValid(context.Context, string) bool 18 | } 19 | -------------------------------------------------------------------------------- /internal/usecase/repo/scene_lock.go: -------------------------------------------------------------------------------- 1 | package repo 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/reearth/reearth-backend/pkg/id" 7 | "github.com/reearth/reearth-backend/pkg/scene" 8 | ) 9 | 10 | type SceneLock interface { 11 | GetLock(context.Context, id.SceneID) (scene.LockMode, error) 12 | GetAllLock(context.Context, id.SceneIDList) ([]scene.LockMode, error) 13 | SaveLock(context.Context, id.SceneID, scene.LockMode) error 14 | ReleaseAllLock(context.Context) error 15 | } 16 | -------------------------------------------------------------------------------- /pkg/user/team.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | type Team struct { 4 | id TeamID 5 | name string 6 | members *Members 7 | } 8 | 9 | func (t *Team) ID() TeamID { 10 | return t.id 11 | } 12 | 13 | func (t *Team) Name() string { 14 | return t.name 15 | } 16 | 17 | func (t *Team) Members() *Members { 18 | return t.members 19 | } 20 | 21 | func (t *Team) IsPersonal() bool { 22 | return t.members.Fixed() 23 | } 24 | 25 | func (t *Team) Rename(name string) { 26 | t.name = name 27 | } 28 | -------------------------------------------------------------------------------- /pkg/asset/id.go: -------------------------------------------------------------------------------- 1 | package asset 2 | 3 | import ( 4 | "github.com/reearth/reearth-backend/pkg/id" 5 | ) 6 | 7 | type ID = id.AssetID 8 | type TeamID = id.TeamID 9 | 10 | var NewID = id.NewAssetID 11 | var NewTeamID = id.NewTeamID 12 | 13 | var MustID = id.MustAssetID 14 | var MustTeamID = id.MustTeamID 15 | 16 | var IDFrom = id.AssetIDFrom 17 | var TeamIDFrom = id.TeamIDFrom 18 | 19 | var IDFromRef = id.AssetIDFromRef 20 | var TeamIDFromRef = id.TeamIDFromRef 21 | 22 | var ErrInvalidID = id.ErrInvalidID 23 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | // List of extensions which should be recommended for users of this workspace. 5 | "recommendations": [ 6 | "golang.go", 7 | "redhat.vscode-yaml" 8 | ], 9 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 10 | "unwantedRecommendations": [] 11 | } 12 | -------------------------------------------------------------------------------- /pkg/project/id.go: -------------------------------------------------------------------------------- 1 | package project 2 | 3 | import ( 4 | "github.com/reearth/reearth-backend/pkg/id" 5 | ) 6 | 7 | type ID = id.ProjectID 8 | type TeamID = id.TeamID 9 | 10 | var NewID = id.NewProjectID 11 | var NewTeamID = id.NewTeamID 12 | 13 | var MustID = id.MustProjectID 14 | var MustTeamID = id.MustTeamID 15 | 16 | var IDFrom = id.ProjectIDFrom 17 | var TeamIDFrom = id.TeamIDFrom 18 | 19 | var IDFromRef = id.ProjectIDFromRef 20 | var TeamIDFromRef = id.TeamIDFromRef 21 | 22 | var ErrInvalidID = id.ErrInvalidID 23 | -------------------------------------------------------------------------------- /pkg/user/id.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import "github.com/reearth/reearth-backend/pkg/id" 4 | 5 | type ID = id.UserID 6 | type TeamID = id.TeamID 7 | 8 | var NewID = id.NewUserID 9 | var NewTeamID = id.NewTeamID 10 | 11 | var MustID = id.MustUserID 12 | var MustTeamID = id.MustTeamID 13 | 14 | var IDFrom = id.UserIDFrom 15 | var TeamIDFrom = id.TeamIDFrom 16 | 17 | var IDFromRef = id.UserIDFromRef 18 | var TeamIDFromRef = id.TeamIDFromRef 19 | 20 | var ErrInvalidID = id.ErrInvalidID 21 | 22 | type TeamIDList = id.TeamIDList 23 | -------------------------------------------------------------------------------- /internal/infrastructure/mailer/logger.go: -------------------------------------------------------------------------------- 1 | package mailer 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/reearth/reearth-backend/internal/usecase/gateway" 7 | ) 8 | 9 | const loggerSep = "=======================" 10 | 11 | type logger struct{} 12 | 13 | func NewLogger() gateway.Mailer { 14 | return &logger{} 15 | } 16 | 17 | func (m *logger) SendMail(to []gateway.Contact, subject, plainContent, _ string) error { 18 | logMail(to, subject) 19 | fmt.Printf("%s\n%s\n%s\n", loggerSep, plainContent, loggerSep) 20 | return nil 21 | } 22 | -------------------------------------------------------------------------------- /internal/infrastructure/mongo/transaction.go: -------------------------------------------------------------------------------- 1 | package mongo 2 | 3 | import ( 4 | "github.com/reearth/reearth-backend/internal/infrastructure/mongo/mongodoc" 5 | "github.com/reearth/reearth-backend/internal/usecase/repo" 6 | ) 7 | 8 | type Transaction struct { 9 | client *mongodoc.Client 10 | } 11 | 12 | func NewTransaction(client *mongodoc.Client) repo.Transaction { 13 | return &Transaction{ 14 | client: client, 15 | } 16 | } 17 | 18 | func (t *Transaction) Begin() (repo.Tx, error) { 19 | return t.client.BeginTransaction() 20 | } 21 | -------------------------------------------------------------------------------- /internal/usecase/repo/auth_request.go: -------------------------------------------------------------------------------- 1 | package repo 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/reearth/reearth-backend/pkg/auth" 7 | "github.com/reearth/reearth-backend/pkg/id" 8 | ) 9 | 10 | type AuthRequest interface { 11 | FindByID(context.Context, id.AuthRequestID) (*auth.Request, error) 12 | FindByCode(context.Context, string) (*auth.Request, error) 13 | FindBySubject(context.Context, string) (*auth.Request, error) 14 | Save(context.Context, *auth.Request) error 15 | Remove(context.Context, id.AuthRequestID) error 16 | } 17 | -------------------------------------------------------------------------------- /pkg/layer/encoding/common_test.go: -------------------------------------------------------------------------------- 1 | package encoding 2 | 3 | import ( 4 | "image/color" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestGetColor(t *testing.T) { 11 | assert.Equal(t, &color.RGBA{R: 255, G: 255, B: 255, A: 255}, getColor("#ffffff")) 12 | assert.Equal(t, &color.RGBA{R: 255, G: 255, B: 255, A: 255}, getColor("#fff")) 13 | assert.Equal(t, &color.RGBA{R: 255, G: 255, B: 255, A: 170}, getColor("#fffa")) 14 | assert.Equal(t, &color.RGBA{R: 255, G: 0, B: 0, A: 170}, getColor("#ff0000aa")) 15 | } 16 | -------------------------------------------------------------------------------- /internal/usecase/pagination.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | type Cursor string 4 | 5 | type Pagination struct { 6 | Before *Cursor 7 | After *Cursor 8 | First *int 9 | Last *int 10 | } 11 | 12 | func NewPagination(first *int, last *int, before *Cursor, after *Cursor) *Pagination { 13 | // Relay-Style Cursor Pagination 14 | // ref: https://www.apollographql.com/docs/react/features/pagination/#relay-style-cursor-pagination 15 | return &Pagination{ 16 | Before: before, 17 | After: after, 18 | First: first, 19 | Last: last, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /pkg/plugin/manifest/testdata/test.yml: -------------------------------------------------------------------------------- 1 | id: aaa 2 | name: bbb 3 | version: 1.1.1 4 | extensions: 5 | - id: hoge 6 | type: primitive 7 | visualizer: cesium 8 | schema: 9 | groups: 10 | - id: default 11 | representativeField: a 12 | fields: 13 | - id: a 14 | type: bool 15 | defaultValue: true 16 | availableIf: 17 | field: b 18 | type: number 19 | value: 1 20 | - id: b 21 | type: number 22 | -------------------------------------------------------------------------------- /internal/usecase/repo/transaction.go: -------------------------------------------------------------------------------- 1 | package repo 2 | 3 | import "context" 4 | 5 | type Transaction interface { 6 | Begin() (Tx, error) 7 | } 8 | 9 | type Tx interface { 10 | // Commit informs Tx to commit when End() is called. 11 | // If this was not called once, rollback is done when End() is called. 12 | Commit() 13 | // End finishes the transaction and do commit if Commit() was called once, or else do rollback. 14 | // This method is supposed to be called in the uscase layer using defer. 15 | End(context.Context) error 16 | IsCommitted() bool 17 | } 18 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | reearth-backend: 4 | build: 5 | context: . 6 | image: reearth/reearth-backend:nightly 7 | environment: 8 | REEARTH_DB: mongodb://reearth-mongo 9 | ports: 10 | - '8080:8080' 11 | # env_file: 12 | # - ./.env 13 | links: 14 | - reearth-mongo 15 | depends_on: 16 | - reearth-mongo 17 | volumes: 18 | - ./data:/reearth/data 19 | reearth-mongo: 20 | image: mongo:4.4-focal 21 | ports: 22 | - 27017:27017 23 | volumes: 24 | - ./mongo:/data/db 25 | -------------------------------------------------------------------------------- /internal/app/profiler.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "cloud.google.com/go/profiler" 5 | "github.com/reearth/reearth-backend/pkg/log" 6 | ) 7 | 8 | func initProfiler(kind string, version string) { 9 | if kind == "" { 10 | return 11 | } 12 | 13 | if kind == "gcp" { 14 | initGCPProfiler(version) 15 | } 16 | 17 | log.Infof("profiler: %s initialized\n", kind) 18 | } 19 | 20 | func initGCPProfiler(version string) { 21 | if err := profiler.Start(profiler.Config{ 22 | Service: "reearth-backend", 23 | ServiceVersion: version, 24 | }); err != nil { 25 | log.Fatalln(err) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/adapter/gql/resolver_user.go: -------------------------------------------------------------------------------- 1 | package gql 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/reearth/reearth-backend/internal/adapter/gql/gqlmodel" 7 | ) 8 | 9 | func (r *Resolver) Me() MeResolver { 10 | return &meResolver{r} 11 | } 12 | 13 | type meResolver struct{ *Resolver } 14 | 15 | func (r *meResolver) MyTeam(ctx context.Context, obj *gqlmodel.Me) (*gqlmodel.Team, error) { 16 | return dataloaders(ctx).Team.Load(obj.MyTeamID) 17 | } 18 | 19 | func (r *meResolver) Teams(ctx context.Context, obj *gqlmodel.Me) ([]*gqlmodel.Team, error) { 20 | return loaders(ctx).Team.FindByUser(ctx, obj.ID) 21 | } 22 | -------------------------------------------------------------------------------- /internal/usecase/repo/team.go: -------------------------------------------------------------------------------- 1 | package repo 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/reearth/reearth-backend/pkg/id" 7 | "github.com/reearth/reearth-backend/pkg/user" 8 | ) 9 | 10 | type Team interface { 11 | FindByUser(context.Context, id.UserID) (user.TeamList, error) 12 | FindByIDs(context.Context, id.TeamIDList) (user.TeamList, error) 13 | FindByID(context.Context, id.TeamID) (*user.Team, error) 14 | Save(context.Context, *user.Team) error 15 | SaveAll(context.Context, []*user.Team) error 16 | Remove(context.Context, id.TeamID) error 17 | RemoveAll(context.Context, id.TeamIDList) error 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/renovate.yml: -------------------------------------------------------------------------------- 1 | name: renovate 2 | on: 3 | push: 4 | branches: 5 | - renovate/* 6 | jobs: 7 | renovate-go-sum-fix: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: checkout 11 | uses: actions/checkout@v3 12 | with: 13 | fetch-depth: 2 14 | - name: fix 15 | uses: at-wat/go-sum-fix-action@v0 16 | with: 17 | git_user: ${{ github.actor }} 18 | git_email: ${{ github.actor }}@users.noreply.github.com 19 | github_token: ${{ secrets.GITHUB_TOKEN }} 20 | commit_style: squash 21 | push: force 22 | -------------------------------------------------------------------------------- /internal/usecase/repo/scene.go: -------------------------------------------------------------------------------- 1 | package repo 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/reearth/reearth-backend/pkg/id" 7 | "github.com/reearth/reearth-backend/pkg/scene" 8 | ) 9 | 10 | type Scene interface { 11 | Filtered(TeamFilter) Scene 12 | FindByID(context.Context, id.SceneID) (*scene.Scene, error) 13 | FindByIDs(context.Context, id.SceneIDList) (scene.List, error) 14 | FindByTeam(context.Context, ...id.TeamID) (scene.List, error) 15 | FindByProject(context.Context, id.ProjectID) (*scene.Scene, error) 16 | Save(context.Context, *scene.Scene) error 17 | Remove(context.Context, id.SceneID) error 18 | } 19 | -------------------------------------------------------------------------------- /pkg/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "sort" 4 | 5 | type Config struct { 6 | Migration int64 7 | Auth *Auth 8 | } 9 | 10 | type Auth struct { 11 | Cert string 12 | Key string 13 | } 14 | 15 | func (c *Config) NextMigrations(migrations []int64) []int64 { 16 | migrations2 := append([]int64{}, migrations...) 17 | sort.SliceStable(migrations2, func(i, j int) bool { return migrations2[i] < migrations2[j] }) 18 | 19 | for i, m := range migrations2 { 20 | if len(migrations2) <= i { 21 | return nil 22 | } 23 | if c.Migration < m { 24 | return migrations2[i:] 25 | } 26 | } 27 | 28 | return nil 29 | } 30 | -------------------------------------------------------------------------------- /pkg/scene/plugin_test.go: -------------------------------------------------------------------------------- 1 | package scene 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestPlugin(t *testing.T) { 10 | pid := MustPluginID("xxx~1.1.1") 11 | pr := NewPropertyID().Ref() 12 | 13 | res := NewPlugin(pid, pr) 14 | assert.Equal(t, &Plugin{ 15 | plugin: pid, 16 | property: pr, 17 | }, res) 18 | assert.Equal(t, pid, res.Plugin()) 19 | assert.Equal(t, &pid, res.PluginRef()) 20 | assert.Equal(t, pr, res.Property()) 21 | 22 | cl := res.Clone() 23 | assert.Equal(t, res, cl) 24 | assert.NotSame(t, res, cl) 25 | 26 | assert.Nil(t, (*Plugin)(nil).PluginRef()) 27 | } 28 | -------------------------------------------------------------------------------- /pkg/property/schema_field_ui_test.go: -------------------------------------------------------------------------------- 1 | package property 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestSchemaFieldUI(t *testing.T) { 10 | var uir *SchemaFieldUI 11 | assert.Equal(t, SchemaFieldUI(""), SchemaFieldUIFrom("")) 12 | assert.Equal(t, uir, SchemaFieldUIFromRef(nil)) 13 | ui := SchemaFieldUILayer 14 | assert.Equal(t, SchemaFieldUILayer, SchemaFieldUIFrom("layer")) 15 | assert.Equal(t, "layer", SchemaFieldUIFrom("layer").String()) 16 | str := "layer" 17 | assert.Equal(t, &ui, SchemaFieldUIFromRef(&str)) 18 | assert.Equal(t, &str, SchemaFieldUIFromRef(&str).StringRef()) 19 | } 20 | -------------------------------------------------------------------------------- /pkg/user/auth.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | type Auth struct { 8 | Provider string 9 | Sub string 10 | } 11 | 12 | func AuthFromAuth0Sub(sub string) Auth { 13 | s := strings.SplitN(sub, "|", 2) 14 | if len(s) != 2 { 15 | return Auth{Provider: "", Sub: sub} 16 | } 17 | return Auth{Provider: s[0], Sub: sub} 18 | } 19 | 20 | func (a Auth) IsAuth0() bool { 21 | return a.Provider == "auth0" 22 | } 23 | 24 | func (a Auth) Ref() *Auth { 25 | a2 := a 26 | return &a2 27 | } 28 | 29 | func GenReearthSub(userID string) *Auth { 30 | return &Auth{ 31 | Provider: "reearth", 32 | Sub: "reearth|" + userID, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /pkg/id/idx/id_test.go: -------------------------------------------------------------------------------- 1 | package idx 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | type TID = ID[T] 10 | 11 | // T is a dummy ID type for unit tests 12 | type T struct{} 13 | 14 | func (T) Type() string { return "_" } 15 | 16 | var idstr = mustParseID("01fzxycwmq7n84q8kessktvb8z") 17 | 18 | func TestID_String(t *testing.T) { 19 | assert.Equal(t, "01fzxycwmq7n84q8kessktvb8z", TID{id: idstr}.String()) 20 | assert.Equal(t, "", ID[T]{}.String()) 21 | } 22 | 23 | func TestID_GoString(t *testing.T) { 24 | assert.Equal(t, "_ID(01fzxycwmq7n84q8kessktvb8z)", TID{id: idstr}.GoString()) 25 | assert.Equal(t, "_ID()", TID{}.GoString()) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/property/validator.go: -------------------------------------------------------------------------------- 1 | package property 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | ) 7 | 8 | type Validator struct { 9 | SchemaLoader SchemaLoader 10 | } 11 | 12 | func (v Validator) Validate(ctx context.Context, properties List) error { 13 | schemaIDs := properties.Schemas() 14 | schemas, err := v.SchemaLoader(ctx, schemaIDs...) 15 | if err != nil { 16 | return err 17 | } 18 | schemaMap := schemas.Map() 19 | 20 | for _, p := range properties { 21 | schema := schemaMap[p.Schema()] 22 | if err := p.ValidateSchema(schema); err != nil { 23 | return fmt.Errorf("invalid property: %s (%s): %w", p.ID(), p.Schema(), err) 24 | } 25 | } 26 | 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /internal/usecase/repo/plugin.go: -------------------------------------------------------------------------------- 1 | package repo 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/reearth/reearth-backend/pkg/id" 7 | "github.com/reearth/reearth-backend/pkg/plugin" 8 | ) 9 | 10 | type Plugin interface { 11 | Filtered(SceneFilter) Plugin 12 | FindByID(context.Context, id.PluginID) (*plugin.Plugin, error) 13 | FindByIDs(context.Context, []id.PluginID) ([]*plugin.Plugin, error) 14 | Save(context.Context, *plugin.Plugin) error 15 | Remove(context.Context, id.PluginID) error 16 | } 17 | 18 | func PluginLoaderFrom(r Plugin) plugin.Loader { 19 | return func(ctx context.Context, ids []id.PluginID) ([]*plugin.Plugin, error) { 20 | return r.FindByIDs(ctx, ids) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /pkg/shp/errreader.go: -------------------------------------------------------------------------------- 1 | package shp 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | // errReader is a helper to perform multiple successive read from another reader 9 | // and do the error checking only once afterwards. It will not perform any new 10 | // reads in case there was an error encountered earlier. 11 | type errReader struct { 12 | io.Reader 13 | e error 14 | n int64 15 | } 16 | 17 | func (er *errReader) Read(p []byte) (n int, err error) { 18 | if er.e != nil { 19 | return 0, fmt.Errorf("unable to read after previous error: %v", er.e) 20 | } 21 | n, err = er.Reader.Read(p) 22 | if n < len(p) && err != nil { 23 | er.e = err 24 | } 25 | er.n += int64(n) 26 | return n, er.e 27 | } 28 | -------------------------------------------------------------------------------- /pkg/tag/tag_test.go: -------------------------------------------------------------------------------- 1 | package tag 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestToTagGroup(t *testing.T) { 10 | tag := Item{} 11 | group := ToTagGroup(&tag) 12 | assert.Nil(t, group) 13 | tag2 := Group{} 14 | group2 := ToTagGroup(&tag2) 15 | assert.NotNil(t, group2) 16 | } 17 | 18 | func TestToTagItem(t *testing.T) { 19 | tag := Group{} 20 | item := ToTagItem(&tag) 21 | assert.Nil(t, item) 22 | tag2 := Item{} 23 | item2 := ToTagItem(&tag2) 24 | assert.NotNil(t, item2) 25 | } 26 | 27 | func TestTag_Rename(t *testing.T) { 28 | tt := tag{ 29 | label: "xxx", 30 | } 31 | tt.Rename("changed") 32 | assert.Equal(t, "changed", tt.Label()) 33 | } 34 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.18-alpine AS build 2 | ARG TAG=release 3 | ARG REV 4 | ARG VERSION 5 | 6 | RUN apk add --update --no-cache git ca-certificates build-base 7 | 8 | COPY go.mod go.sum main.go /reearth/ 9 | WORKDIR /reearth 10 | RUN go mod download 11 | 12 | COPY cmd/ /reearth/cmd/ 13 | COPY pkg/ /reearth/pkg/ 14 | COPY internal/ /reearth/internal/ 15 | 16 | RUN CGO_ENABLED=0 go build -tags "${TAG}" "-ldflags=-X main.version=${VERSION} -s -w -buildid=" -trimpath ./cmd/reearth 17 | 18 | FROM scratch 19 | 20 | COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 21 | COPY --from=build /reearth/reearth /reearth/reearth 22 | 23 | WORKDIR /reearth 24 | 25 | CMD [ "./reearth" ] 26 | -------------------------------------------------------------------------------- /internal/infrastructure/memory/container.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "github.com/reearth/reearth-backend/internal/usecase/repo" 5 | ) 6 | 7 | func New() *repo.Container { 8 | c := &repo.Container{} 9 | c.Asset = NewAsset() 10 | c.Config = NewConfig() 11 | c.DatasetSchema = NewDatasetSchema() 12 | c.Dataset = NewDataset() 13 | c.Layer = NewLayer() 14 | c.Plugin = NewPlugin() 15 | c.Project = NewProject() 16 | c.PropertySchema = NewPropertySchema() 17 | c.Property = NewProperty() 18 | c.Scene = NewScene() 19 | c.Tag = NewTag() 20 | c.Team = NewTeam() 21 | c.User = NewUser() 22 | c.SceneLock = NewSceneLock() 23 | c.Transaction = NewTransaction() 24 | c.Lock = NewLock() 25 | return c 26 | } 27 | -------------------------------------------------------------------------------- /pkg/property/item_builder.go: -------------------------------------------------------------------------------- 1 | package property 2 | 3 | type ItemBuilder struct { 4 | base itemBase 5 | } 6 | 7 | func NewItem() *ItemBuilder { 8 | return &ItemBuilder{} 9 | } 10 | 11 | func (b *ItemBuilder) Group() *GroupBuilder { 12 | return NewGroup().base(b.base) 13 | } 14 | 15 | func (b *ItemBuilder) GroupList() *GroupListBuilder { 16 | return NewGroupList().base(b.base) 17 | } 18 | 19 | func (b *ItemBuilder) ID(id ItemID) *ItemBuilder { 20 | b.base.ID = id 21 | return b 22 | } 23 | 24 | func (b *ItemBuilder) NewID() *ItemBuilder { 25 | b.base.ID = NewItemID() 26 | return b 27 | } 28 | 29 | func (b *ItemBuilder) SchemaGroup(g SchemaGroupID) *ItemBuilder { 30 | b.base.SchemaGroup = g 31 | return b 32 | } 33 | -------------------------------------------------------------------------------- /pkg/id/idx/string.go: -------------------------------------------------------------------------------- 1 | package idx 2 | 3 | type StringID[T Type] string 4 | 5 | func StringIDFromRef[T Type](id *string) *StringID[T] { 6 | if id == nil { 7 | return nil 8 | } 9 | id2 := StringID[T](*id) 10 | return &id2 11 | } 12 | 13 | func (id StringID[T]) Ref() *StringID[T] { 14 | if id == "" { 15 | return nil 16 | } 17 | return &id 18 | } 19 | 20 | func (id *StringID[T]) CloneRef() *StringID[T] { 21 | if id == nil { 22 | return nil 23 | } 24 | id2 := *id 25 | return &id2 26 | } 27 | 28 | func (id StringID[_]) String() string { 29 | return string(id) 30 | } 31 | 32 | func (id *StringID[_]) StringRef() *string { 33 | if id == nil { 34 | return nil 35 | } 36 | id2 := string(*id) 37 | return &id2 38 | } 39 | -------------------------------------------------------------------------------- /internal/infrastructure/mongo/migration/migrations.go: -------------------------------------------------------------------------------- 1 | // Code generated by migrategen, DO NOT EDIT. 2 | 3 | package migration 4 | 5 | // To add a new migration, run go run ./tools/cmd/migrategen migration_name 6 | 7 | // WARNING: 8 | // If the migration takes too long, the deployment may fail in a serverless environment. 9 | // Set the batch size to as large a value as possible without using up the RAM of the deployment destination. 10 | var migrations = map[int64]MigrationFunc{ 11 | 201217132559: AddSceneWidgetId, 12 | 201217193948: AddSceneDefaultTile, 13 | 210310145844: RemovePreviewToken, 14 | 210730175108: AddSceneAlignSystem, 15 | 220214180713: SplitSchemaOfProperties, 16 | 220309174648: AddSceneFieldToPropertySchema, 17 | } 18 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: reearth-backend 2 | before: 3 | hooks: 4 | - go mod tidy 5 | builds: 6 | - main: ./cmd/reearth 7 | flags: 8 | - -tags=release 9 | - -trimpath 10 | ldflags: 11 | - -s -w 12 | - -X main.version={{.Version}} 13 | - -buildid= 14 | env: 15 | - CGO_ENABLED=0 16 | archives: 17 | - name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" 18 | replacements: 19 | darwin: darwin 20 | linux: linux 21 | windows: windows 22 | 386: i386 23 | amd64: x86_64 24 | format_overrides: 25 | - goos: windows 26 | format: zip 27 | changelog: 28 | skip: true 29 | release: 30 | disable: true 31 | -------------------------------------------------------------------------------- /pkg/asset/sort_type.go: -------------------------------------------------------------------------------- 1 | package asset 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | var ( 9 | SortTypeID = SortType("id") 10 | SortTypeName = SortType("name") 11 | SortTypeSize = SortType("size") 12 | 13 | ErrInvalidSortType = errors.New("invalid sort type") 14 | ) 15 | 16 | type SortType string 17 | 18 | func check(role SortType) bool { 19 | switch role { 20 | case SortTypeID: 21 | return true 22 | case SortTypeName: 23 | return true 24 | case SortTypeSize: 25 | return true 26 | } 27 | return false 28 | } 29 | 30 | func SortTypeFromString(r string) (SortType, error) { 31 | role := SortType(strings.ToLower(r)) 32 | 33 | if check(role) { 34 | return role, nil 35 | } 36 | return role, ErrInvalidSortType 37 | } 38 | -------------------------------------------------------------------------------- /pkg/layer/encoding/encoder.go: -------------------------------------------------------------------------------- 1 | package encoding 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/reearth/reearth-backend/pkg/layer/merging" 7 | ) 8 | 9 | var encoders = map[string]func(w io.Writer) Encoder{ 10 | "kml": func(w io.Writer) Encoder { return NewKMLEncoder(w) }, 11 | "geojson": func(w io.Writer) Encoder { return NewGeoJSONEncoder(w) }, 12 | "czml": func(w io.Writer) Encoder { return NewCZMLEncoder(w) }, 13 | "shp": func(w io.Writer) Encoder { return NewSHPEncoder(w) }, 14 | } 15 | 16 | type Encoder interface { 17 | Encode(merging.SealedLayer) error 18 | MimeType() string 19 | } 20 | 21 | func EncoderFromExt(ext string, w io.Writer) Encoder { 22 | e := encoders[ext] 23 | if e == nil { 24 | return nil 25 | } 26 | return e(w) 27 | } 28 | -------------------------------------------------------------------------------- /internal/usecase/interfaces/common.go: -------------------------------------------------------------------------------- 1 | package interfaces 2 | 3 | import "errors" 4 | 5 | type ListOperation string 6 | 7 | const ( 8 | ListOperationAdd ListOperation = "add" 9 | ListOperationMove ListOperation = "move" 10 | ListOperationRemove ListOperation = "remove" 11 | ) 12 | 13 | var ( 14 | ErrSceneIsLocked error = errors.New("scene is locked") 15 | ErrOperationDenied error = errors.New("operation denied") 16 | ErrFileNotIncluded error = errors.New("file not included") 17 | ) 18 | 19 | type Container struct { 20 | Asset Asset 21 | Dataset Dataset 22 | Layer Layer 23 | Plugin Plugin 24 | Project Project 25 | Property Property 26 | Published Published 27 | Scene Scene 28 | Tag Tag 29 | Team Team 30 | User User 31 | } 32 | -------------------------------------------------------------------------------- /internal/usecase/repo/asset.go: -------------------------------------------------------------------------------- 1 | package repo 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/reearth/reearth-backend/internal/usecase" 7 | "github.com/reearth/reearth-backend/pkg/asset" 8 | "github.com/reearth/reearth-backend/pkg/id" 9 | ) 10 | 11 | type AssetFilter struct { 12 | Sort *asset.SortType 13 | Keyword *string 14 | Pagination *usecase.Pagination 15 | } 16 | 17 | type Asset interface { 18 | Filtered(TeamFilter) Asset 19 | FindByTeam(context.Context, id.TeamID, AssetFilter) ([]*asset.Asset, *usecase.PageInfo, error) 20 | FindByID(context.Context, id.AssetID) (*asset.Asset, error) 21 | FindByIDs(context.Context, id.AssetIDList) ([]*asset.Asset, error) 22 | Save(context.Context, *asset.Asset) error 23 | Remove(context.Context, id.AssetID) error 24 | } 25 | -------------------------------------------------------------------------------- /pkg/value/latlng_test.go: -------------------------------------------------------------------------------- 1 | package value 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestLatLng_Clone(t *testing.T) { 10 | tests := []struct { 11 | Name string 12 | LL, Expected *LatLng 13 | }{ 14 | { 15 | Name: "nil latlng", 16 | }, 17 | { 18 | Name: "cloned", 19 | LL: &LatLng{ 20 | Lat: 10, 21 | Lng: 11, 22 | }, 23 | Expected: &LatLng{ 24 | Lat: 10, 25 | Lng: 11, 26 | }, 27 | }, 28 | } 29 | 30 | for _, tc := range tests { 31 | tc := tc 32 | t.Run(tc.Name, func(t *testing.T) { 33 | t.Parallel() 34 | res := tc.LL.Clone() 35 | assert.Equal(t, tc.Expected, res) 36 | if tc.Expected != nil { 37 | assert.NotSame(t, tc.Expected, res) 38 | } 39 | }) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/stage.yml: -------------------------------------------------------------------------------- 1 | name: Stage 2 | on: 3 | workflow_dispatch: 4 | jobs: 5 | stage: 6 | runs-on: ubuntu-latest 7 | if: github.ref == 'refs/heads/main' 8 | steps: 9 | - name: git config 10 | env: 11 | GPT_USER: ${{ secrets.GPT_USER }} 12 | run: | 13 | git config --global user.name $GPT_USER 14 | git config --global pull.rebase false 15 | - uses: actions/checkout@v3 16 | with: 17 | fetch-depth: 0 18 | token: ${{ secrets.GPT }} 19 | - name: Checkout release branch 20 | run: git switch release || git switch -c release 21 | - name: Merge main branch to release branch 22 | run: git merge -X theirs main 23 | - name: Git push 24 | run: git push origin release 25 | -------------------------------------------------------------------------------- /.github/workflows/pr_title.yml: -------------------------------------------------------------------------------- 1 | name: PR Title Checker 2 | on: 3 | pull_request: 4 | types: 5 | - opened 6 | - edited 7 | - synchronize 8 | - labeled 9 | - unlabeled 10 | jobs: 11 | pr_title: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: amannn/action-semantic-pull-request@v4 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | ignoreLabels: meta 18 | scopes: | 19 | web 20 | server 21 | subjectPattern: ^(?![A-Z]).+$ 22 | subjectPatternError: | 23 | The subject "{subject}" found in the pull request title "{title}" 24 | didn't match the configured pattern. Please ensure that the subject 25 | doesn't start with an uppercase character. 26 | -------------------------------------------------------------------------------- /pkg/id/idx/string_test.go: -------------------------------------------------------------------------------- 1 | package idx 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/samber/lo" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestStringID_Ref(t *testing.T) { 11 | assert.Equal(t, lo.ToPtr(StringID[T]("a")), StringID[T]("a").Ref()) 12 | } 13 | 14 | func TestStringID_CloneRef(t *testing.T) { 15 | s := lo.ToPtr(StringID[T]("a")) 16 | res := s.CloneRef() 17 | assert.Equal(t, s, res) 18 | assert.NotSame(t, s, res) 19 | assert.Nil(t, (*StringID[T])(nil).CloneRef()) 20 | } 21 | 22 | func TestStringID_String(t *testing.T) { 23 | assert.Equal(t, "a", StringID[T]("a").String()) 24 | } 25 | 26 | func TestStringID_StringRef(t *testing.T) { 27 | assert.Equal(t, lo.ToPtr("a"), lo.ToPtr(StringID[T]("a")).StringRef()) 28 | assert.Nil(t, (*StringID[T])(nil).StringRef()) 29 | } 30 | -------------------------------------------------------------------------------- /internal/adapter/gql/resolver_project.go: -------------------------------------------------------------------------------- 1 | package gql 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/reearth/reearth-backend/internal/adapter/gql/gqlmodel" 7 | "github.com/reearth/reearth-backend/pkg/rerror" 8 | ) 9 | 10 | func (r *Resolver) Project() ProjectResolver { 11 | return &projectResolver{r} 12 | } 13 | 14 | type projectResolver struct{ *Resolver } 15 | 16 | func (r *projectResolver) Team(ctx context.Context, obj *gqlmodel.Project) (*gqlmodel.Team, error) { 17 | return dataloaders(ctx).Team.Load(obj.TeamID) 18 | } 19 | 20 | func (r *projectResolver) Scene(ctx context.Context, obj *gqlmodel.Project) (*gqlmodel.Scene, error) { 21 | s, err := loaders(ctx).Scene.FindByProject(ctx, obj.ID) 22 | if err != nil && err != rerror.ErrNotFound { 23 | return nil, err 24 | } 25 | return s, nil 26 | } 27 | -------------------------------------------------------------------------------- /pkg/plugin/manifest/manifest.go: -------------------------------------------------------------------------------- 1 | package manifest 2 | 3 | import ( 4 | "github.com/reearth/reearth-backend/pkg/plugin" 5 | "github.com/reearth/reearth-backend/pkg/property" 6 | ) 7 | 8 | type Manifest struct { 9 | Plugin *plugin.Plugin 10 | ExtensionSchema property.SchemaList 11 | Schema *property.Schema 12 | } 13 | 14 | func (m Manifest) PropertySchemas() property.SchemaList { 15 | sl := append(property.SchemaList{}, m.ExtensionSchema...) 16 | if m.Schema != nil { 17 | sl = append(sl, m.Schema) 18 | } 19 | return sl 20 | } 21 | 22 | func (m Manifest) PropertySchema(psid property.SchemaID) *property.Schema { 23 | if psid.IsNil() { 24 | return nil 25 | } 26 | if m.Schema != nil && psid.Equal(m.Schema.ID()) { 27 | return m.Schema 28 | } 29 | return m.ExtensionSchema.Find(psid) 30 | } 31 | -------------------------------------------------------------------------------- /pkg/property/schema_pointer_test.go: -------------------------------------------------------------------------------- 1 | package property 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestSchemaFieldPointer_Pointer(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | target *SchemaFieldPointer 13 | want *Pointer 14 | }{ 15 | { 16 | name: "ok", 17 | target: &SchemaFieldPointer{ 18 | SchemaGroup: SchemaGroupID("a"), 19 | Field: FieldID("b"), 20 | }, 21 | want: &Pointer{ 22 | schemaGroup: SchemaGroupID("a").Ref(), 23 | item: nil, 24 | field: FieldID("b").Ref(), 25 | }, 26 | }, 27 | } 28 | 29 | for _, tt := range tests { 30 | tt := tt 31 | t.Run(tt.name, func(t *testing.T) { 32 | t.Parallel() 33 | assert.Equal(t, tt.want, tt.target.Pointer()) 34 | }) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /internal/infrastructure/gcs/file_test.go: -------------------------------------------------------------------------------- 1 | package gcs 2 | 3 | import ( 4 | "net/url" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestGetGCSObjectURL(t *testing.T) { 11 | e, _ := url.Parse("https://hoge.com/assets/xxx.yyy") 12 | b, _ := url.Parse("https://hoge.com/assets") 13 | assert.Equal(t, e, getGCSObjectURL(b, "xxx.yyy")) 14 | } 15 | 16 | func TestGetGCSObjectNameFromURL(t *testing.T) { 17 | u, _ := url.Parse("https://hoge.com/assets/xxx.yyy") 18 | b, _ := url.Parse("https://hoge.com") 19 | b2, _ := url.Parse("https://hoge2.com") 20 | assert.Equal(t, "assets/xxx.yyy", getGCSObjectNameFromURL(b, u)) 21 | assert.Equal(t, "", getGCSObjectNameFromURL(b2, u)) 22 | assert.Equal(t, "", getGCSObjectNameFromURL(nil, u)) 23 | assert.Equal(t, "", getGCSObjectNameFromURL(b, nil)) 24 | } 25 | -------------------------------------------------------------------------------- /internal/usecase/repo/user.go: -------------------------------------------------------------------------------- 1 | package repo 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/reearth/reearth-backend/pkg/id" 7 | "github.com/reearth/reearth-backend/pkg/user" 8 | ) 9 | 10 | type User interface { 11 | FindByIDs(context.Context, id.UserIDList) ([]*user.User, error) 12 | FindByID(context.Context, id.UserID) (*user.User, error) 13 | FindByAuth0Sub(context.Context, string) (*user.User, error) 14 | FindByEmail(context.Context, string) (*user.User, error) 15 | FindByName(context.Context, string) (*user.User, error) 16 | FindByNameOrEmail(context.Context, string) (*user.User, error) 17 | FindByVerification(context.Context, string) (*user.User, error) 18 | FindByPasswordResetRequest(context.Context, string) (*user.User, error) 19 | Save(context.Context, *user.User) error 20 | Remove(context.Context, id.UserID) error 21 | } 22 | -------------------------------------------------------------------------------- /pkg/property/condition_test.go: -------------------------------------------------------------------------------- 1 | package property 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestCondition_Clone(t *testing.T) { 10 | tests := []struct { 11 | Name string 12 | Con, Expected *Condition 13 | }{ 14 | { 15 | Name: "nil condition", 16 | Con: nil, 17 | Expected: nil, 18 | }, 19 | { 20 | Name: "nil condition", 21 | Con: &Condition{ 22 | Field: "a", 23 | Value: ValueTypeBool.ValueFrom(true), 24 | }, 25 | Expected: &Condition{ 26 | Field: "a", 27 | Value: ValueTypeBool.ValueFrom(true), 28 | }, 29 | }, 30 | } 31 | 32 | for _, tt := range tests { 33 | tt := tt 34 | t.Run(tt.Name, func(t *testing.T) { 35 | t.Parallel() 36 | res := tt.Con.Clone() 37 | assert.Equal(t, tt.Expected, res) 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /internal/adapter/gql/gqlmodel/convert_asset.go: -------------------------------------------------------------------------------- 1 | package gqlmodel 2 | 3 | import ( 4 | "github.com/reearth/reearth-backend/pkg/asset" 5 | ) 6 | 7 | func ToAsset(a *asset.Asset) *Asset { 8 | if a == nil { 9 | return nil 10 | } 11 | 12 | return &Asset{ 13 | ID: IDFrom(a.ID()), 14 | CreatedAt: a.CreatedAt(), 15 | TeamID: IDFrom(a.Team()), 16 | Name: a.Name(), 17 | Size: a.Size(), 18 | URL: a.URL(), 19 | ContentType: a.ContentType(), 20 | } 21 | } 22 | 23 | func AssetSortTypeFrom(ast *AssetSortType) *asset.SortType { 24 | if ast == nil { 25 | return nil 26 | } 27 | 28 | switch *ast { 29 | case AssetSortTypeDate: 30 | return &asset.SortTypeID 31 | case AssetSortTypeName: 32 | return &asset.SortTypeName 33 | case AssetSortTypeSize: 34 | return &asset.SortTypeSize 35 | } 36 | return &asset.SortTypeID 37 | } 38 | -------------------------------------------------------------------------------- /internal/infrastructure/mongo/mongodoc/config.go: -------------------------------------------------------------------------------- 1 | package mongodoc 2 | 3 | import "github.com/reearth/reearth-backend/pkg/config" 4 | 5 | type ConfigDocument struct { 6 | Migration int64 7 | Auth *Auth 8 | } 9 | 10 | type Auth struct { 11 | Cert string 12 | Key string 13 | } 14 | 15 | func NewConfig(c config.Config) ConfigDocument { 16 | d := ConfigDocument{ 17 | Migration: c.Migration, 18 | } 19 | if c.Auth != nil { 20 | d.Auth = &Auth{ 21 | Cert: c.Auth.Cert, 22 | Key: c.Auth.Key, 23 | } 24 | } 25 | return d 26 | } 27 | 28 | func (c *ConfigDocument) Model() *config.Config { 29 | if c == nil { 30 | return &config.Config{} 31 | } 32 | m := &config.Config{ 33 | Migration: c.Migration, 34 | } 35 | if c.Auth != nil { 36 | m.Auth = &config.Auth{ 37 | Cert: c.Auth.Cert, 38 | Key: c.Auth.Key, 39 | } 40 | } 41 | return m 42 | } 43 | -------------------------------------------------------------------------------- /internal/usecase/interfaces/plugin.go: -------------------------------------------------------------------------------- 1 | package interfaces 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "io" 7 | "net/url" 8 | 9 | "github.com/reearth/reearth-backend/internal/usecase" 10 | "github.com/reearth/reearth-backend/pkg/id" 11 | "github.com/reearth/reearth-backend/pkg/plugin" 12 | "github.com/reearth/reearth-backend/pkg/scene" 13 | ) 14 | 15 | var ( 16 | ErrPluginAlreadyRegistered = errors.New("plugin already registered") 17 | ErrInvalidPluginPackage = errors.New("invalid plugin package") 18 | ) 19 | 20 | type Plugin interface { 21 | Fetch(context.Context, []id.PluginID, *usecase.Operator) ([]*plugin.Plugin, error) 22 | Upload(context.Context, io.Reader, id.SceneID, *usecase.Operator) (*plugin.Plugin, *scene.Scene, error) 23 | UploadFromRemote(context.Context, *url.URL, id.SceneID, *usecase.Operator) (*plugin.Plugin, *scene.Scene, error) 24 | } 25 | -------------------------------------------------------------------------------- /pkg/plugin/id.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import "github.com/reearth/reearth-backend/pkg/id" 4 | 5 | type ID = id.PluginID 6 | type ExtensionID = id.PluginExtensionID 7 | type PropertySchemaID = id.PropertySchemaID 8 | type SceneID = id.SceneID 9 | 10 | var NewID = id.NewPluginID 11 | var NewSceneID = id.NewSceneID 12 | var NewPropertySchemaID = id.NewPropertySchemaID 13 | 14 | var MustID = id.MustPluginID 15 | var MustSceneID = id.MustSceneID 16 | var MustPropertySchemaID = id.MustPropertySchemaID 17 | 18 | var IDFrom = id.PluginIDFrom 19 | var SceneIDFrom = id.SceneIDFrom 20 | var PropertySchemaIDFrom = id.PropertySchemaIDFrom 21 | 22 | var IDFromRef = id.PluginIDFromRef 23 | var SceneIDFromRef = id.SceneIDFromRef 24 | var PropertySchemaIDFromRef = id.PropertySchemaIDFromRef 25 | 26 | var OfficialPluginID = id.OfficialPluginID 27 | var ErrInvalidID = id.ErrInvalidID 28 | -------------------------------------------------------------------------------- /pkg/value/latlngheight_test.go: -------------------------------------------------------------------------------- 1 | package value 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestLatLngHeight_Clone(t *testing.T) { 10 | tests := []struct { 11 | Name string 12 | LL, Expected *LatLngHeight 13 | }{ 14 | { 15 | Name: "nil LatLngHeight", 16 | }, 17 | { 18 | Name: "cloned", 19 | LL: &LatLngHeight{ 20 | Lat: 10, 21 | Lng: 11, 22 | Height: 12, 23 | }, 24 | Expected: &LatLngHeight{ 25 | Lat: 10, 26 | Lng: 11, 27 | Height: 12, 28 | }, 29 | }, 30 | } 31 | 32 | for _, tc := range tests { 33 | tc := tc 34 | t.Run(tc.Name, func(t *testing.T) { 35 | t.Parallel() 36 | res := tc.LL.Clone() 37 | assert.Equal(t, tc.Expected, res) 38 | if tc.Expected != nil { 39 | assert.NotSame(t, tc.Expected, res) 40 | } 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /pkg/value/ref.go: -------------------------------------------------------------------------------- 1 | package value 2 | 3 | import "fmt" 4 | 5 | var TypeRef Type = "ref" 6 | 7 | type propertyRef struct{} 8 | 9 | func (*propertyRef) I2V(i interface{}) (interface{}, bool) { 10 | if v, ok := i.(string); ok { 11 | return v, true 12 | } 13 | if v, ok := i.(*string); ok { 14 | return *v, true 15 | } 16 | if v, ok := i.(fmt.Stringer); ok { 17 | return v.String(), true 18 | } 19 | if v, ok := i.(*fmt.Stringer); ok && v != nil { 20 | return (*v).String(), true 21 | } 22 | return nil, false 23 | } 24 | 25 | func (*propertyRef) V2I(v interface{}) (interface{}, bool) { 26 | return v, true 27 | } 28 | 29 | func (*propertyRef) Validate(i interface{}) bool { 30 | _, ok := i.(string) 31 | return ok 32 | } 33 | 34 | func (v *Value) ValueRef() (vv string, ok bool) { 35 | if v == nil { 36 | return 37 | } 38 | vv, ok = v.v.(string) 39 | return 40 | } 41 | -------------------------------------------------------------------------------- /internal/infrastructure/mailer/mock.go: -------------------------------------------------------------------------------- 1 | package mailer 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/reearth/reearth-backend/internal/usecase/gateway" 7 | ) 8 | 9 | type Mock struct { 10 | lock sync.Mutex 11 | mails []Mail 12 | } 13 | 14 | type Mail struct { 15 | To []gateway.Contact 16 | Subject string 17 | PlainContent string 18 | HTMLContent string 19 | } 20 | 21 | func NewMock() *Mock { 22 | return &Mock{} 23 | } 24 | 25 | func (m *Mock) SendMail(to []gateway.Contact, subject, text, html string) error { 26 | m.lock.Lock() 27 | defer m.lock.Unlock() 28 | m.mails = append(m.mails, Mail{ 29 | To: append([]gateway.Contact{}, to...), 30 | Subject: subject, 31 | PlainContent: text, 32 | HTMLContent: html, 33 | }) 34 | return nil 35 | } 36 | 37 | func (m *Mock) Mails() []Mail { 38 | return append([]Mail{}, m.mails...) 39 | } 40 | -------------------------------------------------------------------------------- /internal/infrastructure/memory/layer_test.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/reearth/reearth-backend/pkg/id" 8 | "github.com/reearth/reearth-backend/pkg/layer" 9 | "github.com/reearth/reearth-backend/pkg/tag" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestLayer_FindByTag(t *testing.T) { 14 | ctx := context.Background() 15 | sid := id.NewSceneID() 16 | t1, _ := tag.NewItem().NewID().Scene(sid).Label("item").Build() 17 | tl := layer.NewTagList([]layer.Tag{layer.NewTagGroup(t1.ID(), nil)}) 18 | lg := layer.New().NewID().Tags(tl).Scene(sid).Group().MustBuild() 19 | 20 | repo := Layer{ 21 | data: map[id.LayerID]layer.Layer{ 22 | lg.ID(): lg, 23 | }, 24 | } 25 | 26 | out, err := repo.FindByTag(ctx, t1.ID()) 27 | assert.NoError(t, err) 28 | assert.Equal(t, layer.List{lg.LayerRef()}, out) 29 | } 30 | -------------------------------------------------------------------------------- /pkg/scene/plugin.go: -------------------------------------------------------------------------------- 1 | package scene 2 | 3 | type Plugin struct { 4 | plugin PluginID 5 | property *PropertyID 6 | } 7 | 8 | func NewPlugin(plugin PluginID, property *PropertyID) *Plugin { 9 | return &Plugin{ 10 | plugin: plugin, 11 | property: property.CopyRef(), 12 | } 13 | } 14 | 15 | func (s *Plugin) Plugin() PluginID { 16 | if s == nil { 17 | return PluginID{} 18 | } 19 | return s.plugin 20 | } 21 | 22 | func (s *Plugin) PluginRef() *PluginID { 23 | if s == nil { 24 | return nil 25 | } 26 | return s.plugin.Ref() 27 | } 28 | 29 | func (s *Plugin) Property() *PropertyID { 30 | if s == nil { 31 | return nil 32 | } 33 | return s.property.CopyRef() 34 | } 35 | 36 | func (s *Plugin) Clone() *Plugin { 37 | if s == nil { 38 | return nil 39 | } 40 | return &Plugin{ 41 | plugin: s.plugin.Clone(), 42 | property: s.property.CopyRef(), 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /pkg/tag/map.go: -------------------------------------------------------------------------------- 1 | package tag 2 | 3 | import "sort" 4 | 5 | type Map map[ID]Tag 6 | 7 | func (m Map) All() List { 8 | if m == nil || len(m) == 0 { 9 | return nil 10 | } 11 | res := make(List, 0, len(m)) 12 | for _, t := range m { 13 | res = append(res, t) 14 | } 15 | sort.SliceStable(res, func(i, j int) bool { 16 | return res[i].ID().Compare(res[j].ID()) < 0 17 | }) 18 | return res 19 | } 20 | 21 | func MapFromList(tags []Tag) Map { 22 | res := make(Map) 23 | for _, t := range tags { 24 | if t == nil { 25 | continue 26 | } 27 | 28 | res[t.ID()] = t 29 | } 30 | return res 31 | } 32 | 33 | func MapFromRefList(tags []*Tag) Map { 34 | res := make(Map) 35 | for _, t := range tags { 36 | if t == nil { 37 | continue 38 | } 39 | 40 | t2 := *t 41 | if t2 == nil { 42 | continue 43 | } 44 | 45 | res[t2.ID()] = t2 46 | } 47 | return res 48 | } 49 | -------------------------------------------------------------------------------- /pkg/layer/encoding/common.go: -------------------------------------------------------------------------------- 1 | package encoding 2 | 3 | import ( 4 | "image/color" 5 | "strconv" 6 | "strings" 7 | 8 | "gopkg.in/go-playground/colors.v1" 9 | ) 10 | 11 | func getColor(str string) *color.RGBA { 12 | if len(str) == 0 { 13 | return nil 14 | } 15 | 16 | cs := str 17 | a := "" 18 | 19 | if str[0] == '#' { 20 | if len(str) == 5 { 21 | cs = str[:len(str)-1] 22 | a = strings.Repeat(str[len(str)-1:], 2) 23 | } else if len(str) == 9 { 24 | cs = str[:len(str)-2] 25 | a = str[len(str)-2:] 26 | } 27 | } 28 | 29 | b, err := colors.Parse(cs) 30 | if err != nil || b == nil { 31 | return nil 32 | } 33 | 34 | c := b.ToRGBA() 35 | var alpha uint8 36 | if a != "" { 37 | a2, _ := strconv.ParseUint(a, 16, 8) 38 | alpha = uint8(a2) 39 | } else { 40 | alpha = uint8(c.A * 255) 41 | } 42 | 43 | return &color.RGBA{R: c.R, G: c.G, B: c.B, A: alpha} 44 | } 45 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "go.lintTool": "golangci-lint", 4 | "yaml.format.enable": true, 5 | "yaml.completion": true, 6 | "yaml.validate": true, 7 | "yaml.hover": true, 8 | "yaml.schemas": { 9 | "./schemas/plugin_manifest.json": [ 10 | "/pkg/builtin/manifest.yml" 11 | ], 12 | "./schemas/plugin_manifest_translation.json": [ 13 | "/pkg/builtin/manifest_*.yml" 14 | ], 15 | "https://json.schemastore.org/github-workflow.json": ".github/workflows/build.yml" 16 | }, 17 | "json.schemas": [ 18 | { 19 | "fileMatch": [ 20 | "/pkg/builtin/manifest.json" 21 | ], 22 | "url": "./schemas/plugin_manifest.json" 23 | }, 24 | { 25 | "fileMatch": [ 26 | "/pkg/builtin/manifest_*.json" 27 | ], 28 | "url": "./schemas/plugin_manifest_translation.json" 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /internal/adapter/http/published.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net/url" 7 | 8 | "github.com/reearth/reearth-backend/internal/usecase/interfaces" 9 | ) 10 | 11 | type PublishedController struct { 12 | usecase interfaces.Published 13 | } 14 | 15 | func NewPublishedController(usecase interfaces.Published) *PublishedController { 16 | return &PublishedController{usecase: usecase} 17 | } 18 | 19 | func (c *PublishedController) Metadata(ctx context.Context, name string) (interfaces.ProjectPublishedMetadata, error) { 20 | return c.usecase.Metadata(ctx, name) 21 | } 22 | 23 | func (c *PublishedController) Data(ctx context.Context, name string) (io.Reader, error) { 24 | return c.usecase.Data(ctx, name) 25 | } 26 | 27 | func (c *PublishedController) Index(ctx context.Context, name string, url *url.URL) (string, error) { 28 | return c.usecase.Index(ctx, name, url) 29 | } 30 | -------------------------------------------------------------------------------- /internal/app/private.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | 7 | "github.com/labstack/echo/v4" 8 | "github.com/reearth/reearth-backend/internal/adapter" 9 | "github.com/reearth/reearth-backend/pkg/id" 10 | "github.com/reearth/reearth-backend/pkg/rerror" 11 | ) 12 | 13 | func ExportLayer() echo.HandlerFunc { 14 | return func(c echo.Context) error { 15 | ctx := c.Request().Context() 16 | u := adapter.Usecases(ctx) 17 | 18 | param := c.Param("param") 19 | params := strings.Split(param, ".") 20 | if len(params) != 2 { 21 | return rerror.ErrNotFound 22 | } 23 | 24 | lid, err := id.LayerIDFrom(params[0]) 25 | if err != nil { 26 | return rerror.ErrNotFound 27 | } 28 | 29 | reader, mime, err := u.Layer.Export(ctx, lid, params[1]) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | return c.Stream(http.StatusOK, mime, reader) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /internal/usecase/repo/property_schema.go: -------------------------------------------------------------------------------- 1 | package repo 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/reearth/reearth-backend/pkg/id" 7 | "github.com/reearth/reearth-backend/pkg/property" 8 | ) 9 | 10 | type PropertySchema interface { 11 | Filtered(SceneFilter) PropertySchema 12 | FindByID(context.Context, id.PropertySchemaID) (*property.Schema, error) 13 | FindByIDs(context.Context, []id.PropertySchemaID) (property.SchemaList, error) 14 | Save(context.Context, *property.Schema) error 15 | SaveAll(context.Context, property.SchemaList) error 16 | Remove(context.Context, id.PropertySchemaID) error 17 | RemoveAll(context.Context, []id.PropertySchemaID) error 18 | } 19 | 20 | func PropertySchemaLoaderFrom(r PropertySchema) property.SchemaLoader { 21 | return func(ctx context.Context, ids ...id.PropertySchemaID) (property.SchemaList, error) { 22 | return r.FindByIDs(ctx, ids) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pkg/value/bool.go: -------------------------------------------------------------------------------- 1 | package value 2 | 3 | import "strconv" 4 | 5 | var TypeBool Type = "bool" 6 | 7 | type propertyBool struct{} 8 | 9 | func (p *propertyBool) I2V(i interface{}) (interface{}, bool) { 10 | switch v := i.(type) { 11 | case bool: 12 | return v, true 13 | case string: 14 | if b, err := strconv.ParseBool(v); err == nil { 15 | return b, true 16 | } 17 | case *bool: 18 | if v != nil { 19 | return p.I2V(*v) 20 | } 21 | case *string: 22 | if v != nil { 23 | return p.I2V(*v) 24 | } 25 | } 26 | return nil, false 27 | } 28 | 29 | func (*propertyBool) V2I(v interface{}) (interface{}, bool) { 30 | return v, true 31 | } 32 | 33 | func (*propertyBool) Validate(i interface{}) bool { 34 | _, ok := i.(bool) 35 | return ok 36 | } 37 | 38 | func (v *Value) ValueBool() (vv bool, ok bool) { 39 | if v == nil { 40 | return 41 | } 42 | vv, ok = v.v.(bool) 43 | return 44 | } 45 | -------------------------------------------------------------------------------- /pkg/tag/tag.go: -------------------------------------------------------------------------------- 1 | package tag 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | ErrEmptyLabel = errors.New("tag label can't be empty") 9 | ErrInvalidSceneID = errors.New("invalid scene ID") 10 | ) 11 | 12 | type tag struct { 13 | id ID 14 | label string 15 | sceneId SceneID 16 | } 17 | 18 | type Tag interface { 19 | ID() ID 20 | Scene() SceneID 21 | Label() string 22 | Rename(string) 23 | } 24 | 25 | func (t *tag) ID() ID { 26 | return t.id 27 | } 28 | 29 | func (t *tag) Scene() SceneID { 30 | return t.sceneId 31 | } 32 | 33 | func (t *tag) Label() string { 34 | return t.label 35 | } 36 | 37 | func (t *tag) Rename(s string) { 38 | t.label = s 39 | } 40 | 41 | func ToTagGroup(t Tag) *Group { 42 | if tg, ok := t.(*Group); ok { 43 | return tg 44 | } 45 | return nil 46 | } 47 | 48 | func ToTagItem(t Tag) *Item { 49 | if ti, ok := t.(*Item); ok { 50 | return ti 51 | } 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /pkg/file/targz.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "archive/tar" 5 | "compress/gzip" 6 | "errors" 7 | "io" 8 | ) 9 | 10 | type TarReader struct { 11 | tr *tar.Reader 12 | } 13 | 14 | func NewTarReader(tr *tar.Reader) *TarReader { 15 | return &TarReader{tr: tr} 16 | } 17 | 18 | func TarReaderFromTarGz(r io.Reader) (*TarReader, error) { 19 | gzipReader, err := gzip.NewReader(r) 20 | if err != nil { 21 | return nil, err 22 | } 23 | return &TarReader{tr: tar.NewReader(gzipReader)}, nil 24 | } 25 | 26 | func (r *TarReader) Next() (*File, error) { 27 | if r == nil || r.tr == nil { 28 | return nil, nil 29 | } 30 | 31 | h, err := r.tr.Next() 32 | if errors.Is(err, io.EOF) { 33 | return nil, nil 34 | } 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | fi := h.FileInfo() 40 | if fi.IsDir() { 41 | return r.Next() 42 | } 43 | 44 | return &File{Content: io.NopCloser(r.tr), Path: h.Name, Size: fi.Size()}, nil 45 | } 46 | -------------------------------------------------------------------------------- /pkg/tag/item.go: -------------------------------------------------------------------------------- 1 | package tag 2 | 3 | type Item struct { 4 | tag 5 | parent *ID 6 | linkedDatasetFieldID *DatasetFieldID 7 | linkedDatasetID *DatasetID 8 | linkedDatasetSchemaID *DatasetSchemaID 9 | } 10 | 11 | func (i *Item) Parent() *ID { 12 | if i == nil { 13 | return nil 14 | } 15 | return i.parent.CopyRef() 16 | } 17 | 18 | func (i *Item) LinkedDatasetFieldID() *DatasetFieldID { 19 | if i == nil { 20 | return nil 21 | } 22 | return i.linkedDatasetFieldID.CopyRef() 23 | } 24 | 25 | func (i *Item) LinkedDatasetID() *DatasetID { 26 | if i == nil { 27 | return nil 28 | } 29 | return i.linkedDatasetID.CopyRef() 30 | } 31 | 32 | func (i *Item) LinkedDatasetSchemaID() *DatasetSchemaID { 33 | if i == nil { 34 | return nil 35 | } 36 | return i.linkedDatasetSchemaID.CopyRef() 37 | } 38 | 39 | func (i *Item) SetParent(p *ID) { 40 | if i == nil { 41 | return 42 | } 43 | i.parent = p.CopyRef() 44 | } 45 | -------------------------------------------------------------------------------- /pkg/layer/layerops/processor.go: -------------------------------------------------------------------------------- 1 | package layerops 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/reearth/reearth-backend/pkg/layer" 7 | ) 8 | 9 | type Processor struct { 10 | RootLayerID layer.ID 11 | LayerLoader layer.Loader 12 | } 13 | 14 | type UninstallPluginResult struct { 15 | ModifiedLayers layer.List 16 | RemovedProperties []layer.PropertyID 17 | } 18 | 19 | func (p Processor) UninstallPlugin(ctx context.Context, pluginID layer.PluginID) (res UninstallPluginResult, err error) { 20 | err = p.LayerLoader.Walk(ctx, func(l layer.Layer, parents layer.GroupList) error { 21 | // delete infobox fields 22 | if removedProperties := l.Infobox().RemoveAllByPlugin(pluginID, nil); len(removedProperties) > 0 { 23 | res.RemovedProperties = append(res.RemovedProperties, removedProperties...) 24 | res.ModifiedLayers = append(res.ModifiedLayers, &l) 25 | } 26 | return nil 27 | }, []layer.ID{p.RootLayerID}) 28 | 29 | return 30 | } 31 | -------------------------------------------------------------------------------- /pkg/scene/list.go: -------------------------------------------------------------------------------- 1 | package scene 2 | 3 | type List []*Scene 4 | 5 | func (l List) IDs() []ID { 6 | if l == nil { 7 | return nil 8 | } 9 | 10 | res := make([]ID, 0, len(l)) 11 | for _, s := range l { 12 | res = append(res, s.ID()) 13 | } 14 | return res 15 | } 16 | 17 | func (l List) FilterByID(ids ...ID) List { 18 | if l == nil { 19 | return nil 20 | } 21 | 22 | res := make(List, 0, len(l)) 23 | for _, s := range l { 24 | sid2 := s.ID() 25 | for _, sid := range ids { 26 | if sid == sid2 { 27 | res = append(res, s) 28 | break 29 | } 30 | } 31 | } 32 | return res 33 | } 34 | 35 | func (l List) FilterByTeam(teams ...TeamID) List { 36 | if l == nil { 37 | return nil 38 | } 39 | 40 | res := make(List, 0, len(l)) 41 | for _, s := range l { 42 | st := s.Team() 43 | for _, t := range teams { 44 | if t == st { 45 | res = append(res, s) 46 | break 47 | } 48 | } 49 | } 50 | return res 51 | } 52 | -------------------------------------------------------------------------------- /pkg/user/team_test.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestTeam_ID(t *testing.T) { 10 | tid := NewTeamID() 11 | tm := NewTeam().ID(tid).MustBuild() 12 | assert.Equal(t, tid, tm.ID()) 13 | } 14 | 15 | func TestTeam_Name(t *testing.T) { 16 | tm := NewTeam().NewID().Name("ttt").MustBuild() 17 | assert.Equal(t, "ttt", tm.Name()) 18 | } 19 | 20 | func TestTeam_Members(t *testing.T) { 21 | m := map[ID]Role{ 22 | NewID(): RoleOwner, 23 | } 24 | tm := NewTeam().NewID().Members(m).MustBuild() 25 | assert.Equal(t, m, tm.Members().Members()) 26 | } 27 | 28 | func TestTeam_IsPersonal(t *testing.T) { 29 | tm := NewTeam().NewID().Personal(true).MustBuild() 30 | assert.Equal(t, true, tm.IsPersonal()) 31 | } 32 | 33 | func TestTeam_Rename(t *testing.T) { 34 | tm := NewTeam().NewID().Name("ttt").MustBuild() 35 | tm.Rename("ccc") 36 | assert.Equal(t, "ccc", tm.Name()) 37 | } 38 | -------------------------------------------------------------------------------- /pkg/dataset/loader.go: -------------------------------------------------------------------------------- 1 | package dataset 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type Loader func(context.Context, ...ID) (List, error) 8 | 9 | func LoaderFrom(data []*Dataset) Loader { 10 | return func(ctx context.Context, ids ...ID) (List, error) { 11 | res := make(List, 0, len(ids)) 12 | for _, i := range ids { 13 | found := false 14 | for _, d := range data { 15 | if i == d.ID() { 16 | res = append(res, d) 17 | found = true 18 | break 19 | } 20 | } 21 | if !found { 22 | res = append(res, nil) 23 | } 24 | } 25 | return res, nil 26 | } 27 | } 28 | 29 | func LoaderFromMap(data map[ID]*Dataset) Loader { 30 | return func(ctx context.Context, ids ...ID) (List, error) { 31 | res := make(List, 0, len(ids)) 32 | for _, i := range ids { 33 | if d, ok := data[i]; ok { 34 | res = append(res, d) 35 | } else { 36 | res = append(res, nil) 37 | } 38 | } 39 | return res, nil 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /internal/infrastructure/mongo/mongodoc/consumer.go: -------------------------------------------------------------------------------- 1 | package mongodoc 2 | 3 | import "go.mongodb.org/mongo-driver/bson" 4 | 5 | type Consumer interface { 6 | // Consume で渡されたrawの参照をフィールドに持ってはいけません 7 | // MUST NOT HAVE A ROW REFERENCE PASSED BY Consume METHOD IN THE FIELD 8 | Consume(raw bson.Raw) error 9 | } 10 | 11 | type FuncConsumer func(raw bson.Raw) error 12 | 13 | func (c FuncConsumer) Consume(raw bson.Raw) error { 14 | return c(raw) 15 | } 16 | 17 | type BatchConsumer struct { 18 | Size int 19 | Rows []bson.Raw 20 | Callback func([]bson.Raw) error 21 | } 22 | 23 | func (c *BatchConsumer) Consume(raw bson.Raw) error { 24 | size := c.Size 25 | if size == 0 { 26 | size = 10 27 | } 28 | 29 | if raw != nil { 30 | c.Rows = append(c.Rows, raw) 31 | } 32 | 33 | if raw == nil || len(c.Rows) >= size { 34 | err := c.Callback(c.Rows) 35 | c.Rows = []bson.Raw{} 36 | if err != nil { 37 | return err 38 | } 39 | } 40 | 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /pkg/plugin/manifest/testdata/translation.yml: -------------------------------------------------------------------------------- 1 | { 2 | "description": "test plugin desc", 3 | "name": "test plugin name", 4 | "extensions": 5 | { 6 | "test_ext": 7 | { 8 | "name": "test ext name", 9 | "propertySchema": 10 | { 11 | "test_ps": 12 | { 13 | "description": "test ps desc", 14 | "title": "test ps title", 15 | "fields": 16 | { 17 | "test_field": 18 | { 19 | "title": "test field name", 20 | "description": "test field desc", 21 | "choices": { "test_key": "test choice value" }, 22 | "prefix": "P", 23 | "suffix": "S", 24 | }, 25 | }, 26 | }, 27 | }, 28 | }, 29 | }, 30 | } 31 | -------------------------------------------------------------------------------- /pkg/plugin/list.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import "sort" 4 | 5 | type List []*Plugin 6 | 7 | func (l List) Find(p ID) *Plugin { 8 | for _, q := range l { 9 | if q.ID().Equal(p) { 10 | return q 11 | } 12 | } 13 | return nil 14 | } 15 | 16 | func (l List) Concat(m List) List { 17 | return append(l, m...) 18 | } 19 | 20 | func (l List) MapToIDs(ids []ID) List { 21 | res := make(List, 0, len(ids)) 22 | for _, id := range ids { 23 | res = append(res, l.Find(id)) 24 | } 25 | return res 26 | } 27 | 28 | func (l List) Map() Map { 29 | m := make(Map, len(l)) 30 | for _, p := range l { 31 | m[p.ID()] = p 32 | } 33 | return m 34 | } 35 | 36 | type Map map[ID]*Plugin 37 | 38 | func (m Map) List() List { 39 | if m == nil { 40 | return nil 41 | } 42 | res := make(List, 0, len(m)) 43 | for _, p := range m { 44 | res = append(res, p) 45 | } 46 | sort.SliceStable(res, func(i, j int) bool { 47 | return res[i].ID().String() > res[j].ID().String() 48 | }) 49 | return res 50 | } 51 | -------------------------------------------------------------------------------- /pkg/user/password_reset.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/google/uuid" 7 | ) 8 | 9 | var timeNow = time.Now 10 | 11 | type PasswordReset struct { 12 | Token string 13 | CreatedAt time.Time 14 | } 15 | 16 | func NewPasswordReset() *PasswordReset { 17 | return &PasswordReset{ 18 | Token: generateToken(), 19 | CreatedAt: timeNow(), 20 | } 21 | } 22 | 23 | func PasswordResetFrom(token string, createdAt time.Time) *PasswordReset { 24 | return &PasswordReset{ 25 | Token: token, 26 | CreatedAt: createdAt, 27 | } 28 | } 29 | 30 | func generateToken() string { 31 | return uuid.New().String() 32 | } 33 | 34 | func (pr *PasswordReset) Validate(token string) bool { 35 | return pr != nil && pr.Token == token && pr.CreatedAt.Add(24*time.Hour).After(time.Now()) 36 | } 37 | 38 | func (pr *PasswordReset) Clone() *PasswordReset { 39 | if pr == nil { 40 | return nil 41 | } 42 | pr2 := PasswordResetFrom(pr.Token, pr.CreatedAt) 43 | return pr2 44 | } 45 | -------------------------------------------------------------------------------- /pkg/dataset/schema_field_diff.go: -------------------------------------------------------------------------------- 1 | package dataset 2 | 3 | type SchemaFieldDiff struct { 4 | Added []*SchemaField 5 | Removed []*SchemaField 6 | Replaced map[FieldID]*SchemaField 7 | } 8 | 9 | func (d *Schema) FieldDiffBySource(d2 *Schema) SchemaFieldDiff { 10 | added := []*SchemaField{} 11 | removed := []*SchemaField{} 12 | // others := map[string]DatasetDiffTouple{} 13 | others2 := map[FieldID]*SchemaField{} 14 | 15 | s1 := map[string]*SchemaField{} 16 | for _, d1 := range d.fields { 17 | s1[d1.Source()] = d1 18 | } 19 | 20 | for _, d2 := range d2.fields { 21 | if d1, ok := s1[d2.Source()]; ok { 22 | others2[d1.ID()] = d2 23 | } else { 24 | // added 25 | added = append(added, d2) 26 | } 27 | } 28 | 29 | for _, d1 := range d.fields { 30 | if _, ok := others2[d1.ID()]; !ok { 31 | // removed 32 | removed = append(removed, d1) 33 | } 34 | } 35 | 36 | return SchemaFieldDiff{ 37 | Added: added, 38 | Removed: removed, 39 | Replaced: others2, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /internal/infrastructure/memory/transaction_test.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestTransaction_Committed(t *testing.T) { 12 | tr := NewTransaction() 13 | tx, err := tr.Begin() 14 | assert.NoError(t, err) 15 | assert.Equal(t, 0, tr.Committed()) 16 | tx.Commit() 17 | assert.Equal(t, 0, tr.Committed()) 18 | assert.NoError(t, tx.End(context.Background())) 19 | assert.Equal(t, 1, tr.Committed()) 20 | } 21 | 22 | func TestTransaction_SetBeginError(t *testing.T) { 23 | err := errors.New("a") 24 | tr := NewTransaction() 25 | tr.SetBeginError(err) 26 | tx, err2 := tr.Begin() 27 | assert.Nil(t, tx) 28 | assert.Same(t, err, err2) 29 | } 30 | 31 | func TestTransaction_SetEndError(t *testing.T) { 32 | err := errors.New("a") 33 | tr := NewTransaction() 34 | tr.SetEndError(err) 35 | tx, err2 := tr.Begin() 36 | assert.NoError(t, err2) 37 | assert.Same(t, err, tx.End(context.Background())) 38 | } 39 | -------------------------------------------------------------------------------- /pkg/scene/cluster.go: -------------------------------------------------------------------------------- 1 | package scene 2 | 3 | type Cluster struct { 4 | id ClusterID 5 | name string 6 | property PropertyID 7 | } 8 | 9 | func NewCluster(cid ClusterID, name string, pid PropertyID) (*Cluster, error) { 10 | if cid.IsNil() { 11 | return nil, ErrInvalidID 12 | } 13 | return &Cluster{ 14 | id: cid, 15 | name: name, 16 | property: pid, 17 | }, nil 18 | } 19 | 20 | func (c *Cluster) ID() ClusterID { 21 | if c == nil { 22 | return ClusterID{} 23 | } 24 | return c.id 25 | } 26 | 27 | func (c *Cluster) Name() string { 28 | if c == nil { 29 | return "" 30 | } 31 | return c.name 32 | } 33 | 34 | func (c *Cluster) Property() PropertyID { 35 | if c == nil { 36 | return PropertyID{} 37 | } 38 | return c.property 39 | } 40 | 41 | func (c *Cluster) Rename(name string) { 42 | if c == nil { 43 | return 44 | } 45 | c.name = name 46 | } 47 | 48 | func (c *Cluster) UpdateProperty(pid PropertyID) { 49 | if c == nil { 50 | return 51 | } 52 | c.property = pid 53 | } 54 | -------------------------------------------------------------------------------- /internal/infrastructure/memory/config.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | 7 | "github.com/reearth/reearth-backend/internal/usecase/repo" 8 | "github.com/reearth/reearth-backend/pkg/config" 9 | ) 10 | 11 | type Config struct { 12 | lock sync.Mutex 13 | locked bool 14 | data *config.Config 15 | } 16 | 17 | func NewConfig() repo.Config { 18 | return &Config{} 19 | } 20 | 21 | func (r *Config) LockAndLoad(ctx context.Context) (*config.Config, error) { 22 | r.lock.Lock() 23 | r.locked = true 24 | return r.data, nil 25 | } 26 | 27 | func (r *Config) Save(ctx context.Context, c *config.Config) error { 28 | if c != nil { 29 | r.data = c 30 | } 31 | return nil 32 | } 33 | 34 | func (r *Config) SaveAndUnlock(ctx context.Context, c *config.Config) error { 35 | _ = r.Save(ctx, c) 36 | return r.Unlock(ctx) 37 | } 38 | 39 | func (r *Config) Unlock(_ context.Context) error { 40 | if !r.locked { 41 | return nil 42 | } 43 | r.lock.Unlock() 44 | r.locked = false 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /pkg/dataset/graph_loader.go: -------------------------------------------------------------------------------- 1 | package dataset 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type GraphLoader func(context.Context, ID, ...FieldID) (List, *Field, error) 8 | 9 | func GraphLoaderFromMap(m Map) GraphLoader { 10 | return func(ctx context.Context, root ID, fields ...FieldID) (List, *Field, error) { 11 | list, field := m.GraphSearchByFields(root, fields...) 12 | return list, field, nil 13 | } 14 | } 15 | 16 | func GraphLoaderFromMapAndGraph(m Map, g GraphLoader) GraphLoader { 17 | return func(ctx context.Context, root ID, fields ...FieldID) (List, *Field, error) { 18 | if m != nil { 19 | if len(fields) == 0 { 20 | return List{m[root]}, nil, nil 21 | } 22 | if len(fields) == 1 { 23 | ds := m[root] 24 | return List{ds}, ds.Field(fields[0]), nil 25 | } 26 | list, field := m.GraphSearchByFields(root, fields...) 27 | if list != nil && field != nil { 28 | return list, field, nil 29 | } 30 | } 31 | 32 | // it needs looking up dataset graph 33 | return g(ctx, root, fields...) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /pkg/file/zip_test.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "io" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestMockZipReader(t *testing.T) { 11 | z := MockZipReader([]string{"a", "b", "c/", "c/d"}) 12 | assert.Equal(t, "a", z.File[0].Name) 13 | assert.Equal(t, "b", z.File[1].Name) 14 | assert.Equal(t, "c/", z.File[2].Name) 15 | assert.Equal(t, "c/d", z.File[3].Name) 16 | 17 | for _, f := range []string{"a", "b", "c/d"} { 18 | zf, err := z.Open(f) 19 | assert.NoError(t, err) 20 | b, err := io.ReadAll(zf) 21 | assert.NoError(t, err) 22 | assert.Equal(t, []byte{}, b) 23 | assert.NoError(t, zf.Close()) 24 | } 25 | } 26 | 27 | func TestZipBasePath(t *testing.T) { 28 | assert.Equal(t, "aaa", ZipBasePath(MockZipReader([]string{"aaa/", "aaa/a"}))) 29 | assert.Equal(t, "", ZipBasePath(MockZipReader([]string{"aaa/", "aaa/a", "b"}))) 30 | assert.Equal(t, "", ZipBasePath(MockZipReader([]string{"aaa"}))) 31 | assert.Equal(t, "", ZipBasePath(MockZipReader([]string{"aaa/", "aaa/a", "b/", "b/c"}))) 32 | } 33 | -------------------------------------------------------------------------------- /pkg/property/value_camera_test.go: -------------------------------------------------------------------------------- 1 | package property 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestCamera_Clone(t *testing.T) { 10 | tests := []struct { 11 | Name string 12 | Camera, Expected *Camera 13 | }{ 14 | { 15 | Name: "nil Camera", 16 | }, 17 | { 18 | Name: "cloned", 19 | Camera: &Camera{ 20 | Lat: 1, 21 | Lng: 1, 22 | Altitude: 2, 23 | Heading: 4, 24 | Pitch: 5, 25 | Roll: 6, 26 | FOV: 7, 27 | }, 28 | Expected: &Camera{ 29 | Lat: 1, 30 | Lng: 1, 31 | Altitude: 2, 32 | Heading: 4, 33 | Pitch: 5, 34 | Roll: 6, 35 | FOV: 7, 36 | }, 37 | }, 38 | } 39 | 40 | for _, tc := range tests { 41 | tc := tc 42 | t.Run(tc.Name, func(t *testing.T) { 43 | t.Parallel() 44 | res := tc.Camera.Clone() 45 | assert.Equal(t, tc.Expected, res) 46 | if tc.Expected != nil { 47 | assert.NotSame(t, tc.Expected, res) 48 | } 49 | }) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /internal/infrastructure/mailer/sendgrid.go: -------------------------------------------------------------------------------- 1 | package mailer 2 | 3 | import ( 4 | "github.com/reearth/reearth-backend/internal/usecase/gateway" 5 | "github.com/sendgrid/sendgrid-go" 6 | "github.com/sendgrid/sendgrid-go/helpers/mail" 7 | ) 8 | 9 | type sendgridMailer struct { 10 | name string 11 | email string 12 | client *sendgrid.Client 13 | } 14 | 15 | func NewSendGrid(senderName, senderEmail, api string) gateway.Mailer { 16 | return &sendgridMailer{ 17 | name: senderName, 18 | email: senderEmail, 19 | client: sendgrid.NewSendClient(api), 20 | } 21 | } 22 | 23 | func (m *sendgridMailer) SendMail(to []gateway.Contact, subject, plainContent, htmlContent string) error { 24 | for _, t := range to { 25 | sender := mail.NewEmail(m.name, m.email) 26 | receiver := mail.NewEmail(t.Name, t.Email) 27 | message := mail.NewSingleEmail(sender, subject, receiver, plainContent, htmlContent) 28 | _, err := m.client.Send(message) 29 | if err != nil { 30 | return err 31 | } 32 | } 33 | 34 | logMail(to, subject) 35 | return nil 36 | } 37 | -------------------------------------------------------------------------------- /internal/adapter/gql/gqlmodel/convert_property_test.go: -------------------------------------------------------------------------------- 1 | package gqlmodel 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/reearth/reearth-backend/pkg/property" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestFromPropertyValueAndType(t *testing.T) { 12 | type args struct { 13 | v interface{} 14 | t ValueType 15 | } 16 | 17 | tests := []struct { 18 | name string 19 | args args 20 | want *property.Value 21 | }{ 22 | { 23 | name: "number", 24 | args: args{ 25 | v: 1.1, 26 | t: ValueTypeNumber, 27 | }, 28 | want: property.ValueTypeNumber.ValueFrom(1.1), 29 | }, 30 | { 31 | name: "json number", 32 | args: args{ 33 | v: json.Number("1.1"), 34 | t: ValueTypeNumber, 35 | }, 36 | want: property.ValueTypeNumber.ValueFrom(1.1), 37 | }, 38 | } 39 | 40 | for _, tt := range tests { 41 | tt := tt 42 | t.Run(tt.name, func(t *testing.T) { 43 | t.Parallel() 44 | assert.Equal(t, tt.want, FromPropertyValueAndType(tt.args.v, tt.args.t)) 45 | }) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /pkg/asset/asset.go: -------------------------------------------------------------------------------- 1 | package asset 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | ) 7 | 8 | var ( 9 | ErrEmptyTeamID = errors.New("require team id") 10 | ErrEmptyURL = errors.New("require valid url") 11 | ErrEmptySize = errors.New("file size cannot be zero") 12 | ) 13 | 14 | type Asset struct { 15 | id ID 16 | createdAt time.Time 17 | team TeamID 18 | name string // file name 19 | size int64 // file size 20 | url string 21 | contentType string 22 | } 23 | 24 | func (a *Asset) ID() ID { 25 | return a.id 26 | } 27 | 28 | func (a *Asset) Team() TeamID { 29 | return a.team 30 | } 31 | 32 | func (a *Asset) Name() string { 33 | return a.name 34 | } 35 | 36 | func (a *Asset) Size() int64 { 37 | return a.size 38 | } 39 | 40 | func (a *Asset) URL() string { 41 | return a.url 42 | } 43 | 44 | func (a *Asset) ContentType() string { 45 | return a.contentType 46 | } 47 | 48 | func (a *Asset) CreatedAt() time.Time { 49 | if a == nil { 50 | return time.Time{} 51 | } 52 | return a.id.Timestamp() 53 | } 54 | -------------------------------------------------------------------------------- /pkg/dataset/field.go: -------------------------------------------------------------------------------- 1 | package dataset 2 | 3 | type Field struct { 4 | field FieldID 5 | value *Value 6 | source string 7 | } 8 | 9 | func NewField(field FieldID, value *Value, source string) *Field { 10 | if value == nil { 11 | return nil 12 | } 13 | return &Field{ 14 | field: field, 15 | value: value, 16 | } 17 | } 18 | 19 | func (d *Field) Field() (i FieldID) { 20 | if d == nil { 21 | return 22 | } 23 | return d.field 24 | } 25 | 26 | func (d *Field) FieldRef() *FieldID { 27 | if d == nil { 28 | return nil 29 | } 30 | return d.field.Ref() 31 | } 32 | 33 | func (d *Field) IsEmpty() bool { 34 | return d == nil || d.field.IsNil() || d.value == nil 35 | } 36 | 37 | func (d *Field) Value() *Value { 38 | if d == nil { 39 | return nil 40 | } 41 | return d.value.Clone() 42 | } 43 | 44 | func (d *Field) Type() ValueType { 45 | if d == nil { 46 | return ValueTypeUnknown 47 | } 48 | return d.value.Type() 49 | } 50 | 51 | func (d *Field) Source() string { 52 | if d == nil { 53 | return "" 54 | } 55 | return d.source 56 | } 57 | -------------------------------------------------------------------------------- /internal/infrastructure/google/fetch.go: -------------------------------------------------------------------------------- 1 | package google 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "net/url" 8 | ) 9 | 10 | func sheetURL(fileId string, sheetName string) string { 11 | gurl := url.URL{ 12 | Scheme: "https", 13 | Host: "docs.google.com", 14 | Path: fmt.Sprintf("spreadsheets/d/%s/gviz/tq", fileId), 15 | } 16 | 17 | queryValues := gurl.Query() 18 | queryValues.Set("tqx", "out:csv") 19 | queryValues.Set("sheet", sheetName) 20 | gurl.RawQuery = queryValues.Encode() 21 | 22 | return gurl.String() 23 | } 24 | 25 | func fetchCSV(token string, fileId string, sheetName string) (*io.ReadCloser, error) { 26 | u := sheetURL(fileId, sheetName) 27 | req, err := http.NewRequest("GET", u, nil) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | req.Header.Set("Authorization", "Bearer "+token) 33 | res, err := http.DefaultClient.Do(req) 34 | if err != nil { 35 | return nil, err 36 | } 37 | if res.StatusCode != http.StatusOK { 38 | return nil, fmt.Errorf("StatusCode=%d", res.StatusCode) 39 | } 40 | 41 | return &res.Body, nil 42 | } 43 | -------------------------------------------------------------------------------- /tools/cmd/shapefiletest/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "strconv" 6 | 7 | "github.com/jonas-p/go-shp" 8 | ) 9 | 10 | func main() { 11 | // points to write 12 | points := []shp.Point{ 13 | {X: 10.0, Y: 10.0}, 14 | {X: 10.0, Y: 15.0}, 15 | {X: 15.0, Y: 15.0}, 16 | {X: 15.0, Y: 10.0}, 17 | } 18 | 19 | // fields to write 20 | fields := []shp.Field{ 21 | // String attribute field with length 25 22 | shp.StringField("NAME", 25), 23 | } 24 | 25 | // create and open a shapefile for writing points 26 | shape, err := shp.Create("points.shp", shp.POINT) 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | defer shape.Close() 31 | 32 | // setup fields for attributes 33 | if err := shape.SetFields(fields); err != nil { 34 | log.Fatal(err) 35 | } 36 | 37 | // write points and attributes 38 | for n, point := range points { 39 | shape.Write(&point) 40 | 41 | // write attribute for object n for field 0 (NAME) 42 | if err := shape.WriteAttribute(n, 0, "Point "+strconv.Itoa(n+1)); err != nil { 43 | log.Fatal(err) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /pkg/value/url.go: -------------------------------------------------------------------------------- 1 | package value 2 | 3 | import "net/url" 4 | 5 | var TypeURL Type = "url" 6 | 7 | type propertyURL struct{} 8 | 9 | func (p *propertyURL) I2V(i interface{}) (interface{}, bool) { 10 | if v, ok := i.(url.URL); ok { 11 | return &v, true 12 | } 13 | 14 | if v, ok := i.(*url.URL); ok && v != nil { 15 | return p.I2V(*v) // clone URL 16 | } 17 | 18 | if v, ok := i.(string); ok { 19 | if u, err := url.Parse(v); err == nil { 20 | return u, true 21 | } 22 | } 23 | 24 | if v, ok := i.(*string); ok && v != nil { 25 | return p.I2V(*v) 26 | } 27 | 28 | return nil, false 29 | } 30 | 31 | func (*propertyURL) V2I(v interface{}) (interface{}, bool) { 32 | u, ok := v.(*url.URL) 33 | if !ok { 34 | return nil, false 35 | } 36 | if u == nil { 37 | return "", true 38 | } 39 | return u.String(), true 40 | } 41 | 42 | func (*propertyURL) Validate(i interface{}) bool { 43 | _, ok := i.(*url.URL) 44 | return ok 45 | } 46 | 47 | func (v *Value) ValueURL() (vv *url.URL, ok bool) { 48 | if v == nil { 49 | return 50 | } 51 | vv, ok = v.v.(*url.URL) 52 | return 53 | } 54 | -------------------------------------------------------------------------------- /gqlgen.yml: -------------------------------------------------------------------------------- 1 | # .gqlgen.yml example 2 | # 3 | # Refer to https://gqlgen.com/config/ 4 | # for detailed .gqlgen.yml documentation. 5 | 6 | schema: 7 | - schema.graphql 8 | exec: 9 | filename: internal/adapter/gql/generated.go 10 | model: 11 | filename: internal/adapter/gql/gqlmodel/models_gen.go 12 | resolver: 13 | filename: internal/adapter/gql/resolver.go 14 | type: Resolver 15 | models: 16 | DateTime: 17 | model: github.com/99designs/gqlgen/graphql.Time 18 | FileSize: 19 | model: github.com/99designs/gqlgen/graphql.Int64 20 | Cursor: 21 | model: github.com/reearth/reearth-backend/internal/adapter/gql/gqlmodel.Cursor 22 | URL: 23 | model: github.com/reearth/reearth-backend/internal/adapter/gql/gqlmodel.URL 24 | TranslatedString: 25 | model: github.com/reearth/reearth-backend/internal/adapter/gql/gqlmodel.Map 26 | Lang: 27 | model: github.com/reearth/reearth-backend/internal/adapter/gql/gqlmodel.Lang 28 | ID: 29 | model: github.com/reearth/reearth-backend/internal/adapter/gql/gqlmodel.ID 30 | DatasetSchema: 31 | fields: 32 | totalCount: 33 | resolver: true 34 | -------------------------------------------------------------------------------- /internal/app/web.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | "strings" 7 | 8 | "github.com/labstack/echo/v4" 9 | "github.com/labstack/echo/v4/middleware" 10 | ) 11 | 12 | type WebConfig map[string]string 13 | 14 | func web(e *echo.Echo, wc WebConfig, a []AuthConfig) { 15 | if _, err := os.Stat("web"); err != nil { 16 | return // web won't be delivered 17 | } 18 | 19 | e.Logger.Info("web: web directory will be delivered\n") 20 | 21 | config := map[string]string{} 22 | if len(a) > 0 { 23 | ac := a[0] 24 | if ac.ISS != "" { 25 | config["auth0Domain"] = strings.TrimSuffix(ac.ISS, "/") 26 | } 27 | if ac.ClientID != nil { 28 | config["auth0ClientId"] = *ac.ClientID 29 | } 30 | if len(ac.AUD) > 0 { 31 | config["auth0Audience"] = ac.AUD[0] 32 | } 33 | } 34 | for k, v := range wc { 35 | config[k] = v 36 | } 37 | 38 | e.GET("/reearth_config.json", func(c echo.Context) error { 39 | return c.JSON(http.StatusOK, config) 40 | }) 41 | 42 | e.Use(middleware.StaticWithConfig(middleware.StaticConfig{ 43 | Root: "web", 44 | Index: "index.html", 45 | Browse: false, 46 | HTML5: true, 47 | })) 48 | } 49 | -------------------------------------------------------------------------------- /internal/usecase/interfaces/asset.go: -------------------------------------------------------------------------------- 1 | package interfaces 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | "github.com/reearth/reearth-backend/internal/usecase" 8 | "github.com/reearth/reearth-backend/pkg/asset" 9 | "github.com/reearth/reearth-backend/pkg/file" 10 | "github.com/reearth/reearth-backend/pkg/id" 11 | ) 12 | 13 | type AssetFilterType string 14 | 15 | const ( 16 | AssetFilterDate AssetFilterType = "DATE" 17 | AssetFilterSize AssetFilterType = "SIZE" 18 | AssetFilterName AssetFilterType = "NAME" 19 | ) 20 | 21 | type CreateAssetParam struct { 22 | TeamID id.TeamID 23 | File *file.File 24 | } 25 | 26 | var ( 27 | ErrCreateAssetFailed error = errors.New("failed to create asset") 28 | ) 29 | 30 | type Asset interface { 31 | Fetch(context.Context, []id.AssetID, *usecase.Operator) ([]*asset.Asset, error) 32 | FindByTeam(context.Context, id.TeamID, *string, *asset.SortType, *usecase.Pagination, *usecase.Operator) ([]*asset.Asset, *usecase.PageInfo, error) 33 | Create(context.Context, CreateAssetParam, *usecase.Operator) (*asset.Asset, error) 34 | Remove(context.Context, id.AssetID, *usecase.Operator) (id.AssetID, error) 35 | } 36 | -------------------------------------------------------------------------------- /pkg/value/rect.go: -------------------------------------------------------------------------------- 1 | package value 2 | 3 | import "github.com/mitchellh/mapstructure" 4 | 5 | var TypeRect Type = "rect" 6 | 7 | type Rect struct { 8 | West float64 `json:"west" mapstructure:"west"` 9 | South float64 `json:"south" mapstructure:"south"` 10 | East float64 `json:"east" mapstructure:"east"` 11 | North float64 `json:"north" mapstructure:"north"` 12 | } 13 | 14 | type propertyRect struct{} 15 | 16 | func (p *propertyRect) I2V(i interface{}) (interface{}, bool) { 17 | if v, ok := i.(Rect); ok { 18 | return v, true 19 | } else if v, ok := i.(*Rect); ok { 20 | if v != nil { 21 | return p.I2V(*v) 22 | } 23 | return nil, false 24 | } 25 | 26 | v := Rect{} 27 | if err := mapstructure.Decode(i, &v); err == nil { 28 | return v, false 29 | } 30 | 31 | return nil, false 32 | } 33 | 34 | func (*propertyRect) V2I(v interface{}) (interface{}, bool) { 35 | return v, true 36 | } 37 | 38 | func (*propertyRect) Validate(i interface{}) bool { 39 | _, ok := i.(Rect) 40 | return ok 41 | } 42 | 43 | func (v *Value) ValueRect() (vv Rect, ok bool) { 44 | if v == nil { 45 | return 46 | } 47 | vv, ok = v.v.(Rect) 48 | return 49 | } 50 | -------------------------------------------------------------------------------- /pkg/layer/encoding/exporter.go: -------------------------------------------------------------------------------- 1 | package encoding 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/reearth/reearth-backend/pkg/layer" 7 | "github.com/reearth/reearth-backend/pkg/layer/merging" 8 | ) 9 | 10 | type Exporter struct { 11 | Merger *merging.Merger 12 | Sealer *merging.Sealer 13 | Encoder Encoder 14 | } 15 | 16 | func (e *Exporter) ExportLayerByID(ctx context.Context, l layer.ID) error { 17 | if e == nil { 18 | return nil 19 | } 20 | m, err := e.Merger.MergeLayerFromID(ctx, l, nil) 21 | if err != nil { 22 | return err 23 | } 24 | return e.Encode(ctx, m) 25 | } 26 | 27 | func (e *Exporter) ExportLayer(ctx context.Context, l layer.Layer) error { 28 | if e == nil { 29 | return nil 30 | } 31 | m, err := e.Merger.MergeLayer(ctx, l, nil) 32 | if err != nil { 33 | return err 34 | } 35 | return e.Encode(ctx, m) 36 | } 37 | 38 | func (e *Exporter) Encode(ctx context.Context, m merging.MergedLayer) error { 39 | if e == nil { 40 | return nil 41 | } 42 | s, err := e.Sealer.Seal(ctx, m) 43 | if err != nil { 44 | return err 45 | } 46 | err = e.Encoder.Encode(s) 47 | if err != nil { 48 | return err 49 | } 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /pkg/scene/lock.go: -------------------------------------------------------------------------------- 1 | package scene 2 | 3 | // LockMode はシーンのロック状態を表します。 4 | type LockMode string 5 | 6 | const ( 7 | // LockModeFree はロックがかかっていない状態です。 8 | LockModeFree LockMode = "" 9 | // LockModePending は処理待ち中です。データの変更は無制限に変更はできます。 10 | LockModePending LockMode = "pending" 11 | // LockModePluginUpgrading はプラグインをアップグレード中です。シーンへの各種操作ができません。 12 | LockModePluginUpgrading LockMode = "plugin upgrading" 13 | // LockModeDatasetSyncing はデータセットを同期中です。シーンへの各種操作ができません。 14 | LockModeDatasetSyncing LockMode = "dataset syncing" 15 | // LockModePublishing はシーンを書き出し中です。シーンへの各種操作ができません。 16 | LockModePublishing LockMode = "publishing" 17 | ) 18 | 19 | func (l LockMode) IsLocked() bool { 20 | switch l { 21 | case LockModeFree: 22 | return false 23 | case LockModePending: 24 | return false 25 | } 26 | return true 27 | } 28 | 29 | func (l LockMode) Validate() (LockMode, bool) { 30 | switch l { 31 | case LockModeFree: 32 | fallthrough 33 | case LockModePending: 34 | fallthrough 35 | case LockModePluginUpgrading: 36 | fallthrough 37 | case LockModeDatasetSyncing: 38 | fallthrough 39 | case LockModePublishing: 40 | return l, true 41 | } 42 | return l, false 43 | } 44 | -------------------------------------------------------------------------------- /pkg/value/string.go: -------------------------------------------------------------------------------- 1 | package value 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | ) 7 | 8 | var TypeString Type = "string" 9 | 10 | type propertyString struct{} 11 | 12 | func (p *propertyString) I2V(i interface{}) (interface{}, bool) { 13 | if v, ok := i.(string); ok { 14 | return v, true 15 | } else if v, ok := i.(float64); ok { 16 | return strconv.FormatFloat(v, 'f', -1, 64), true 17 | } else if v, ok := i.(bool); ok && v { 18 | return "true", true 19 | } else if v, ok := i.(*string); ok && v != nil { 20 | return p.I2V(*v) 21 | } else if v, ok := i.(*float64); ok && v != nil { 22 | return p.I2V(*v) 23 | } else if v, ok := i.(*bool); ok && v != nil { 24 | return p.I2V(*v) 25 | } else if v, ok := i.(fmt.Stringer); ok && v != nil { 26 | return v.String(), true 27 | } 28 | return nil, false 29 | } 30 | 31 | func (*propertyString) V2I(v interface{}) (interface{}, bool) { 32 | return v, true 33 | } 34 | 35 | func (*propertyString) Validate(i interface{}) bool { 36 | _, ok := i.(string) 37 | return ok 38 | } 39 | 40 | func (v *Value) ValueString() (vv string, ok bool) { 41 | if v == nil { 42 | return 43 | } 44 | vv, ok = v.v.(string) 45 | return 46 | } 47 | -------------------------------------------------------------------------------- /internal/usecase/gateway/file.go: -------------------------------------------------------------------------------- 1 | package gateway 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "io" 7 | "net/url" 8 | 9 | "github.com/reearth/reearth-backend/pkg/file" 10 | "github.com/reearth/reearth-backend/pkg/id" 11 | ) 12 | 13 | var ( 14 | ErrInvalidFile error = errors.New("invalid file") 15 | ErrFailedToUploadFile error = errors.New("failed to upload file") 16 | ErrFileTooLarge error = errors.New("file too large") 17 | ErrFailedToRemoveFile error = errors.New("failed to remove file") 18 | ) 19 | 20 | type File interface { 21 | ReadAsset(context.Context, string) (io.ReadCloser, error) 22 | UploadAsset(context.Context, *file.File) (*url.URL, error) 23 | RemoveAsset(context.Context, *url.URL) error 24 | ReadPluginFile(context.Context, id.PluginID, string) (io.ReadCloser, error) 25 | UploadPluginFile(context.Context, id.PluginID, *file.File) error 26 | RemovePlugin(context.Context, id.PluginID) error 27 | UploadBuiltScene(context.Context, io.Reader, string) error 28 | ReadBuiltSceneFile(context.Context, string) (io.ReadCloser, error) 29 | MoveBuiltScene(context.Context, string, string) error 30 | RemoveBuiltScene(context.Context, string) error 31 | } 32 | -------------------------------------------------------------------------------- /internal/usecase/interactor/team_test.go: -------------------------------------------------------------------------------- 1 | package interactor 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/reearth/reearth-backend/internal/infrastructure/memory" 8 | "github.com/reearth/reearth-backend/internal/usecase" 9 | "github.com/reearth/reearth-backend/pkg/id" 10 | "github.com/reearth/reearth-backend/pkg/user" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestCreateTeam(t *testing.T) { 15 | ctx := context.Background() 16 | 17 | db := memory.New() 18 | 19 | u := user.New().NewID().Email("aaa@bbb.com").Team(id.NewTeamID()).MustBuild() 20 | teamUC := NewTeam(db) 21 | op := &usecase.Operator{User: u.ID()} 22 | team, err := teamUC.Create(ctx, "team name", u.ID(), op) 23 | 24 | assert.Nil(t, err) 25 | assert.NotNil(t, team) 26 | 27 | resultTeams, _ := teamUC.Fetch(ctx, []id.TeamID{team.ID()}, &usecase.Operator{ 28 | ReadableTeams: []id.TeamID{team.ID()}, 29 | }) 30 | 31 | assert.NotNil(t, resultTeams) 32 | assert.NotEmpty(t, resultTeams) 33 | assert.Equal(t, resultTeams[0].ID(), team.ID()) 34 | assert.Equal(t, resultTeams[0].Name(), "team name") 35 | assert.Equal(t, user.TeamIDList{resultTeams[0].ID()}, op.OwningTeams) 36 | } 37 | -------------------------------------------------------------------------------- /pkg/tag/loader.go: -------------------------------------------------------------------------------- 1 | package tag 2 | 3 | import "context" 4 | 5 | type Loader func(context.Context, ...ID) ([]*Tag, error) 6 | type SceneLoader func(context.Context, SceneID) ([]*Tag, error) 7 | 8 | func LoaderFrom(data List) Loader { 9 | return func(ctx context.Context, ids ...ID) ([]*Tag, error) { 10 | res := make([]*Tag, 0, len(ids)) 11 | for _, i := range ids { 12 | found := false 13 | for _, d := range data { 14 | if i == d.ID() { 15 | res = append(res, &d) 16 | found = true 17 | break 18 | } 19 | } 20 | if !found { 21 | res = append(res, nil) 22 | } 23 | } 24 | return res, nil 25 | } 26 | } 27 | 28 | func LoaderFromMap(data map[ID]Tag) Loader { 29 | return func(ctx context.Context, ids ...ID) ([]*Tag, error) { 30 | res := make([]*Tag, 0, len(ids)) 31 | for _, i := range ids { 32 | if d, ok := data[i]; ok { 33 | res = append(res, &d) 34 | } else { 35 | res = append(res, nil) 36 | } 37 | } 38 | return res, nil 39 | } 40 | } 41 | 42 | func SceneLoaderFrom(data List) SceneLoader { 43 | return func(ctx context.Context, id SceneID) ([]*Tag, error) { 44 | return data.FilterByScene(id).Refs(), nil 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /internal/infrastructure/mongo/mongodoc/pagination.go: -------------------------------------------------------------------------------- 1 | package mongodoc 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/reearth/reearth-backend/internal/usecase" 7 | ) 8 | 9 | type Pagination struct { 10 | Before *string 11 | After *string 12 | First *int 13 | Last *int 14 | } 15 | 16 | func PaginationFrom(pagination *usecase.Pagination) *Pagination { 17 | if pagination == nil { 18 | return nil 19 | } 20 | return &Pagination{ 21 | Before: (*string)(pagination.Before), 22 | After: (*string)(pagination.After), 23 | First: pagination.First, 24 | Last: pagination.Last, 25 | } 26 | } 27 | 28 | func (p *Pagination) SortDirection() int { 29 | if p != nil && p.Last != nil { 30 | return -1 31 | } 32 | return 1 33 | } 34 | 35 | func (p *Pagination) Parameters() (limit int64, op string, cursor *string, err error) { 36 | if first, after := p.First, p.After; first != nil { 37 | limit = int64(*first) 38 | op = "$gt" 39 | cursor = after 40 | return 41 | } 42 | if last, before := p.Last, p.Before; last != nil { 43 | limit = int64(*last) 44 | op = "$lt" 45 | cursor = before 46 | return 47 | } 48 | return 0, "", nil, errors.New("neither first nor last are set") 49 | } 50 | -------------------------------------------------------------------------------- /pkg/dataset/schema_field.go: -------------------------------------------------------------------------------- 1 | package dataset 2 | 3 | type SchemaField struct { 4 | id FieldID 5 | name string 6 | dataType ValueType 7 | source string 8 | ref *SchemaID 9 | } 10 | 11 | func (d *SchemaField) ID() (i FieldID) { 12 | if d == nil { 13 | return 14 | } 15 | return d.id 16 | } 17 | 18 | func (d *SchemaField) IDRef() *FieldID { 19 | if d == nil { 20 | return nil 21 | } 22 | return d.id.Ref() 23 | } 24 | 25 | func (d *SchemaField) Name() (n string) { 26 | if d == nil { 27 | return 28 | } 29 | return d.name 30 | } 31 | 32 | func (d *SchemaField) Ref() *SchemaID { 33 | if d == nil { 34 | return nil 35 | } 36 | return d.ref 37 | } 38 | 39 | func (d *SchemaField) Type() (v ValueType) { 40 | if d == nil { 41 | return 42 | } 43 | return d.dataType 44 | } 45 | 46 | func (d *SchemaField) Source() (s string) { 47 | if d == nil { 48 | return 49 | } 50 | return d.source 51 | } 52 | 53 | func (d *SchemaField) Clone() *SchemaField { 54 | if d == nil { 55 | return nil 56 | } 57 | return &SchemaField{ 58 | id: d.id, 59 | name: d.name, 60 | dataType: d.dataType, 61 | source: d.source, 62 | ref: d.ref.CopyRef(), 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /internal/usecase/interactor/published_test.go: -------------------------------------------------------------------------------- 1 | package interactor 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/reearth/reearth-backend/internal/usecase/interfaces" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestRenderIndex(t *testing.T) { 11 | assert.Equal(t, `
12 |