diff --git a/app/javascript/controllers/projects_controller.tsx b/app/javascript/controllers/projects_controller.tsx
index a8feaaf9..b49f5d8a 100644
--- a/app/javascript/controllers/projects_controller.tsx
+++ b/app/javascript/controllers/projects_controller.tsx
@@ -11,6 +11,7 @@ export default class extends Controller {
projectSource: Object,
projectTeamId: Number,
projectTeamName: String,
+ projectDefraHedgerowPermission: Boolean,
backButtonPath: String,
dbModels: Object,
}
@@ -19,10 +20,10 @@ export default class extends Controller {
declare readonly projectSourceValue: Project
declare readonly projectTeamIdValue: number
declare readonly projectTeamNameValue: string
+ declare readonly projectDefraHedgerowPermissionValue: boolean
declare readonly backButtonPathValue: string
declare readonly dbModelsValue: DBModels
-
connect() {
ReactDOM.render(
,
this.element
)
diff --git a/app/javascript/projects/model_view.tsx b/app/javascript/projects/model_view.tsx
index 9d784b47..2c743a01 100644
--- a/app/javascript/projects/model_view.tsx
+++ b/app/javascript/projects/model_view.tsx
@@ -13,6 +13,7 @@ import { NodeComponent } from './node_component'
import { SaveModel } from './modelling/components/save_model_component'
import { getDatasets } from './modelling/components/dataset_component'
import { Extent } from 'ol/extent'
+import { ProjectPermissions } from './project_editor'
// Rete doesn't export `Transform`, so we have to re-define it ourselves
export interface Transform {
@@ -40,8 +41,9 @@ export interface ModelViewProps {
mask: boolean
maskLayer: string
maskCQL: string
+ permissions: ProjectPermissions
}
-export function ModelView({ visible, initialTransform, setTransform, initialModel, setModel, createOutputLayer, deleteOutputLayer, saveMapLayer, setProcessing, autoProcessing, process, setProcess, saveModel, getDatasets, extent, zoom, mask, maskLayer, maskCQL }: ModelViewProps) {
+export function ModelView({ visible, initialTransform, setTransform, initialModel, setModel, createOutputLayer, deleteOutputLayer, saveMapLayer, setProcessing, autoProcessing, process, setProcess, saveModel, getDatasets, extent, zoom, mask, maskLayer, maskCQL, permissions }: ModelViewProps) {
const ref = React.useRef(null)
const [editor, setEditor] = React.useState()
const [engine, setEngine] = React.useState()
@@ -68,7 +70,7 @@ export function ModelView({ visible, initialTransform, setTransform, initialMode
})
const engine = new Engine("landscapes@1.0.0")
- createDefaultComponents(saveMapLayer, saveModel, getDatasets, extent, zoom, mask, maskLayer, maskCQL).forEach(component => {
+ createDefaultComponents(saveMapLayer, saveModel, getDatasets, extent, zoom, mask, maskLayer, maskCQL, permissions).forEach(component => {
editor.register(component)
engine.register(component)
})
diff --git a/app/javascript/projects/modelling/components/hedgerow_component.ts b/app/javascript/projects/modelling/components/hedgerow_component.ts
new file mode 100644
index 00000000..a4e6589e
--- /dev/null
+++ b/app/javascript/projects/modelling/components/hedgerow_component.ts
@@ -0,0 +1,92 @@
+import { NodeData, WorkerInputs, WorkerOutputs } from "rete/types/core/data"
+import { BaseComponent } from "./base_component"
+import { Input, Node, Output } from 'rete'
+import { ProjectProperties } from "./index"
+import { booleanDataSocket } from "../socket_types"
+import { maskFromExtentAndShape } from "../bounding_box"
+import { retrieveModelData } from "../model_retrieval"
+import { createXYZ } from "ol/tilegrid"
+import { TypedArray } from "d3"
+import { BooleanTileGrid } from "../tile_grid"
+
+export class HedgerowComponent extends BaseComponent {
+ ProjectProperties: ProjectProperties
+ cachedHedgerows: BooleanTileGrid
+
+ constructor(projectProps: ProjectProperties) {
+ super("Hedgerows")
+ this.category = "Inputs"
+ this.ProjectProperties = projectProps
+ }
+
+ async builder(node: Node) {
+ node.addOutput(new Output('out', 'Hedgerows', booleanDataSocket))
+ }
+
+ async worker(node: NodeData, inputs: WorkerInputs, outputs: WorkerOutputs, ...args: unknown[]) {
+
+ const editorNode = this.editor?.nodes.find(n => n.id === node.id)
+ if (editorNode === undefined) { return }
+
+ const mask = await maskFromExtentAndShape(
+ this.ProjectProperties.extent,
+ this.ProjectProperties.zoom,
+ this.ProjectProperties.maskLayer,
+ this.ProjectProperties.maskCQL,
+ this.ProjectProperties.mask
+ )
+
+ if (node.outputs['out'].connections.length > 0)
+ {
+
+ if(this.cachedHedgerows === undefined) {
+
+ const tileGrid = createXYZ()
+ const outputTileRange = tileGrid.getTileRangeForExtentAndZ(
+ this.ProjectProperties.extent,
+ this.ProjectProperties.zoom
+ )
+ const geotiff = await retrieveModelData(
+ this.ProjectProperties.extent,
+ 'nateng:defra_lcm_hedges',
+ outputTileRange
+ )
+
+ const rasters = await geotiff.readRasters({
+ bbox: this.ProjectProperties.extent,
+ width: outputTileRange.getWidth(),
+ height: outputTileRange.getHeight()
+ })
+
+ const image = await geotiff.getImage()
+
+ const result = new BooleanTileGrid(
+ this.ProjectProperties.zoom,
+ outputTileRange.minX,
+ outputTileRange.minY,
+ outputTileRange.getWidth(),
+ outputTileRange.getHeight()
+ )
+
+ for (let i = 0; i < (rasters[0] as TypedArray).length; i++) {
+
+ let x = (outputTileRange.minX + i % image.getWidth())
+ let y = (outputTileRange.minY + Math.floor(i / image.getWidth()))
+
+ result.set(x, y, rasters[3][i] === 0 ? false : (mask.get(x, y) === true ? true : false))
+
+ }
+
+ this.cachedHedgerows = result
+ outputs['out'] = result
+
+ }else{
+
+ outputs['out'] = this.cachedHedgerows
+
+ }
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/javascript/projects/modelling/components/index.ts b/app/javascript/projects/modelling/components/index.ts
index 021dbde4..0e5e6239 100644
--- a/app/javascript/projects/modelling/components/index.ts
+++ b/app/javascript/projects/modelling/components/index.ts
@@ -34,6 +34,8 @@ import { ATIComponent } from "./ati_component"
import { DesignationsComponent } from "./designations_component"
import { ORValComponent } from "./orval_component"
import { IMDComponent } from "./imd_component"
+import { HedgerowComponent } from "./hedgerow_component"
+import { ProjectPermissions } from "../../project_editor"
export interface ProjectProperties {
extent: Extent
@@ -43,77 +45,85 @@ export interface ProjectProperties {
maskCQL: string
}
-export function createDefaultComponents(saveMapLayer: SaveMapLayer, saveModel: SaveModel, getDatasets: getDatasets, extent: Extent, zoom: number, mask: boolean, maskLayer: string, maskCQL: string): BaseComponent[] {
-
- const projectProps = { extent, zoom, mask, maskLayer, maskCQL }
-
- return [
- // TODO: Replace extent, mask, zoom, maskLayer, maskCQL with projectProps in all components
-
- // Inputs
- new UkcehLandCoverComponent(extent, zoom, mask, maskLayer, maskCQL),
- new LehLandCoverComponent(extent, zoom, mask, maskLayer, maskCQL),
- new IMDComponent(extent, zoom, mask, maskLayer, maskCQL),
- new MlTreeHedgeComponent(extent, zoom, mask, maskLayer, maskCQL),
- new BiodiversityComponent(extent, zoom, mask, maskLayer, maskCQL),
- new NevoLayerComponent(extent, zoom, mask, maskLayer, maskCQL),
- new ORValComponent(extent, zoom, mask, maskLayer, maskCQL),
- new OSMLandUseComponent(extent, zoom, mask, maskLayer, maskCQL),
- new NumericConstantComponent(),
- new DigitalModelComponent(extent, zoom, mask, maskLayer, maskCQL),
- new PrecompiledModelComponent(getDatasets, extent, zoom, mask, maskLayer, maskCQL),
- new CensusComponent(extent, zoom, mask, maskLayer, maskCQL),
- new OSGreenSpacesComponent(extent, zoom, mask, maskLayer, maskCQL),
- new CROMEComponent(extent, zoom, mask, maskLayer, maskCQL),
- new ATIComponent(extent, zoom, mask, maskLayer, maskCQL),
- new DesignationsComponent(extent, zoom, mask, maskLayer, maskCQL),
-
- // Outputs
- new MapLayerComponent(saveMapLayer),
- new SaveModelOutputComponent(saveModel),
-
- // Conversions
- new NumberToNumericDatasetComponent(),
- new NumericDatasetToNumberComponent(),
- new CategoricalComponent(),
-
- // Calculations
- new AreaComponent(),
- new DistanceMapComponent(extent, zoom, mask, maskLayer, maskCQL),
- new ScaleFactorComponent(),
-
- // Charts
- new BarChartComponent(),
-
- // Set operations
- new VariadicOpComponent('Union', '⋃', booleanDataSocket, booleanDataSocket, 'Set operations'),
- new VariadicOpComponent('Intersection', '⋂', booleanDataSocket, booleanDataSocket, 'Set operations'),
- new BinaryOpComponent('Set difference', '−', booleanDataSocket, booleanDataSocket, 'Set operations', projectProps),
- new VariadicOpComponent('Symmetric difference', 'Δ', booleanDataSocket, booleanDataSocket, 'Set operations'),
- new UnaryOpComponent('Complement', '′', 'postfix', booleanDataSocket, booleanDataSocket, 'Set operations', projectProps),
-
- // Arithmetic
- new MaskNumericDataComponent(),
- new ExpressionComponent(),
- new BinaryOpComponent('Min', '', numericNumberDataSocket, numericNumberDataSocket, 'Arithmetic', projectProps),
- new BinaryOpComponent('Max', '', numericNumberDataSocket, numericNumberDataSocket, 'Arithmetic', projectProps),
- new VariadicOpComponent('Sum', '∑', numericDataSocket, numericDataSocket, 'Arithmetic', 'Sum all inputs'),
- new VariadicOpComponent('Merge', '', numericDataSocket, numericDataSocket, 'Arithmetic', 'Merge all inputs into a single dataset, NaN logic is overridden'),
- new VariadicOpComponent('Product', '∏', numericDataSocket, numericDataSocket, 'Arithmetic'),
- new BinaryOpComponent('Add', '+', numericNumberDataSocket, numericNumberDataSocket, 'Arithmetic', projectProps),
- new BinaryOpComponent('Subtract', '−', numericNumberDataSocket, numericNumberDataSocket, 'Arithmetic', projectProps),
- new BinaryOpComponent('Multiply', '×', numericNumberDataSocket, numericNumberDataSocket, 'Arithmetic', projectProps),
- new BinaryOpComponent('Divide', '÷', numericNumberDataSocket, numericNumberDataSocket, 'Arithmetic', projectProps),
- new BinaryOpComponent('Power', '^', numericNumberDataSocket, numericNumberDataSocket, 'Arithmetic', projectProps),
- new UnaryOpComponent('Negate', '−', 'prefix', numericDataSocket, numericDataSocket, 'Arithmetic', projectProps),
- new UnaryOpComponent('Reciprocal', '⁻¹', 'postfix', numericDataSocket, numericDataSocket, 'Arithmetic', projectProps),
- new BinaryOpComponent('Less', '<', numericNumberDataSocket, booleanDataSocket, 'Arithmetic', projectProps),
- new BinaryOpComponent('Greater', '>', numericNumberDataSocket, booleanDataSocket, 'Arithmetic', projectProps),
- new ReplaceNaNComponent(),
-
- // DEBUG TOOLS
- new CellAreaComponent(),
- new RescaleComponent(),
-
- ]
+export function createDefaultComponents(saveMapLayer: SaveMapLayer, saveModel: SaveModel, getDatasets: getDatasets, extent: Extent, zoom: number, mask: boolean, maskLayer: string, maskCQL: string, permissions: ProjectPermissions): BaseComponent[] {
+
+ const projectProps: ProjectProperties = { extent, zoom, mask, maskLayer, maskCQL }
+
+ const restrictedComponents: BaseComponent[] = []
+
+ // Team permissions restrict some components. Add them here.
+ if (permissions.DefraHedgerows) restrictedComponents.push(new HedgerowComponent(projectProps))
+
+ // Freely available components here.
+ const components : BaseComponent[] = [
+ // TODO: Replace extent, mask, zoom, maskLayer, maskCQL with projectProps in all components
+
+ // Inputs
+ new UkcehLandCoverComponent(extent, zoom, mask, maskLayer, maskCQL),
+ new LehLandCoverComponent(extent, zoom, mask, maskLayer, maskCQL),
+ new IMDComponent(extent, zoom, mask, maskLayer, maskCQL),
+ new MlTreeHedgeComponent(extent, zoom, mask, maskLayer, maskCQL),
+ new BiodiversityComponent(extent, zoom, mask, maskLayer, maskCQL),
+ new NevoLayerComponent(extent, zoom, mask, maskLayer, maskCQL),
+ new ORValComponent(extent, zoom, mask, maskLayer, maskCQL),
+ new OSMLandUseComponent(extent, zoom, mask, maskLayer, maskCQL),
+ new NumericConstantComponent(),
+ new DigitalModelComponent(extent, zoom, mask, maskLayer, maskCQL),
+ new PrecompiledModelComponent(getDatasets, extent, zoom, mask, maskLayer, maskCQL),
+ new CensusComponent(extent, zoom, mask, maskLayer, maskCQL),
+ new OSGreenSpacesComponent(extent, zoom, mask, maskLayer, maskCQL),
+ new CROMEComponent(extent, zoom, mask, maskLayer, maskCQL),
+ new ATIComponent(extent, zoom, mask, maskLayer, maskCQL),
+ new DesignationsComponent(extent, zoom, mask, maskLayer, maskCQL),
+
+ // Outputs
+ new MapLayerComponent(saveMapLayer),
+ new SaveModelOutputComponent(saveModel),
+
+ // Conversions
+ new NumberToNumericDatasetComponent(),
+ new NumericDatasetToNumberComponent(),
+ new CategoricalComponent(),
+
+ // Calculations
+ new AreaComponent(),
+ new DistanceMapComponent(extent, zoom, mask, maskLayer, maskCQL),
+ new ScaleFactorComponent(),
+
+ // Charts
+ new BarChartComponent(),
+
+ // Set operations
+ new VariadicOpComponent('Union', '⋃', booleanDataSocket, booleanDataSocket, 'Set operations'),
+ new VariadicOpComponent('Intersection', '⋂', booleanDataSocket, booleanDataSocket, 'Set operations'),
+ new BinaryOpComponent('Set difference', '−', booleanDataSocket, booleanDataSocket, 'Set operations', projectProps),
+ new VariadicOpComponent('Symmetric difference', 'Δ', booleanDataSocket, booleanDataSocket, 'Set operations'),
+ new UnaryOpComponent('Complement', '′', 'postfix', booleanDataSocket, booleanDataSocket, 'Set operations', projectProps),
+
+ // Arithmetic
+ new MaskNumericDataComponent(),
+ new ExpressionComponent(),
+ new BinaryOpComponent('Min', '', numericNumberDataSocket, numericNumberDataSocket, 'Arithmetic', projectProps),
+ new BinaryOpComponent('Max', '', numericNumberDataSocket, numericNumberDataSocket, 'Arithmetic', projectProps),
+ new VariadicOpComponent('Sum', '∑', numericDataSocket, numericDataSocket, 'Arithmetic', 'Sum all inputs'),
+ new VariadicOpComponent('Merge', '', numericDataSocket, numericDataSocket, 'Arithmetic', 'Merge all inputs into a single dataset, NaN logic is overridden'),
+ new VariadicOpComponent('Product', '∏', numericDataSocket, numericDataSocket, 'Arithmetic'),
+ new BinaryOpComponent('Add', '+', numericNumberDataSocket, numericNumberDataSocket, 'Arithmetic', projectProps),
+ new BinaryOpComponent('Subtract', '−', numericNumberDataSocket, numericNumberDataSocket, 'Arithmetic', projectProps),
+ new BinaryOpComponent('Multiply', '×', numericNumberDataSocket, numericNumberDataSocket, 'Arithmetic', projectProps),
+ new BinaryOpComponent('Divide', '÷', numericNumberDataSocket, numericNumberDataSocket, 'Arithmetic', projectProps),
+ new BinaryOpComponent('Power', '^', numericNumberDataSocket, numericNumberDataSocket, 'Arithmetic', projectProps),
+ new UnaryOpComponent('Negate', '−', 'prefix', numericDataSocket, numericDataSocket, 'Arithmetic', projectProps),
+ new UnaryOpComponent('Reciprocal', '⁻¹', 'postfix', numericDataSocket, numericDataSocket, 'Arithmetic', projectProps),
+ new BinaryOpComponent('Less', '<', numericNumberDataSocket, booleanDataSocket, 'Arithmetic', projectProps),
+ new BinaryOpComponent('Greater', '>', numericNumberDataSocket, booleanDataSocket, 'Arithmetic', projectProps),
+ new ReplaceNaNComponent(),
+
+ // DEBUG TOOLS
+ new CellAreaComponent(),
+ new RescaleComponent(),
+
+ ]
+
+ return components.concat(restrictedComponents)
}
diff --git a/app/javascript/projects/project_editor.tsx b/app/javascript/projects/project_editor.tsx
index 24cbc98d..ac3bdf58 100644
--- a/app/javascript/projects/project_editor.tsx
+++ b/app/javascript/projects/project_editor.tsx
@@ -21,6 +21,10 @@ export enum Tab {
ModelView,
}
+export interface ProjectPermissions {
+ DefraHedgerows: boolean
+}
+
interface ProjectEditorProps {
projectId: number
projectSource: Project
@@ -28,8 +32,9 @@ interface ProjectEditorProps {
dbModels: DBModels
teamId: number
teamName: string
+ permissions: ProjectPermissions
}
-export function ProjectEditor({ projectId, projectSource, backButtonPath, dbModels, teamId, teamName }: ProjectEditorProps) {
+export function ProjectEditor({ projectId, projectSource, backButtonPath, dbModels, teamId, teamName, permissions }: ProjectEditorProps) {
const [state, dispatch] = React.useReducer(reduce, {
project: { ...defaultProject, ...projectSource },
hasUnsavedChanges: false,
@@ -249,6 +254,7 @@ export function ProjectEditor({ projectId, projectSource, backButtonPath, dbMode
mask={projectMask}
maskLayer={projectMaskSource}
maskCQL={projectMaskCQL}
+ permissions={permissions}
/>
diff --git a/app/models/permission.rb b/app/models/permission.rb
new file mode 100644
index 00000000..400170ec
--- /dev/null
+++ b/app/models/permission.rb
@@ -0,0 +1,14 @@
+class Permission < ApplicationRecord
+ has_many :team_permissions, dependent: :destroy
+ has_many :teams, through: :team_permissions
+
+ after_create :assign_to_all_teams
+
+ private
+
+ def assign_to_all_teams
+ Team.all.each do |team|
+ TeamPermission.create(team: team, permission: self, enabled: false)
+ end
+ end
+end
diff --git a/app/models/project.rb b/app/models/project.rb
index 2007ceae..bf0261e3 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -46,4 +46,13 @@ def parse_extent
def duplicate
dup
end
+
+ def defra_hedgerow_permission
+ p = Permission.find_by(name: 'defra_hedgerow')
+ return false unless p
+
+ tp = team.team_permissions.find_by(permission: p)
+ tp ? tp.enabled : false
+ end
+
end
diff --git a/app/models/team.rb b/app/models/team.rb
index b7bfa4bd..e43ddd53 100644
--- a/app/models/team.rb
+++ b/app/models/team.rb
@@ -11,5 +11,19 @@ class Team < ApplicationRecord
has_many :map_tile_layers, through: :regions
has_many :overlays, through: :regions
+ has_many :team_permissions
+ has_many :permissions, through: :team_permissions
+
+ after_create :assign_permissions
+
validates :name, presence: true
+
+ private
+
+ def assign_permissions
+ Permission.all.each do |permission|
+ TeamPermission.create(team: self, permission: permission, enabled: false)
+ end
+ end
+
end
diff --git a/app/models/team_permission.rb b/app/models/team_permission.rb
new file mode 100644
index 00000000..7ad7144d
--- /dev/null
+++ b/app/models/team_permission.rb
@@ -0,0 +1,4 @@
+class TeamPermission < ApplicationRecord
+ belongs_to :team
+ belongs_to :permission
+end
diff --git a/app/views/projects/show.html.erb b/app/views/projects/show.html.erb
index 83bc44a0..a39658cd 100644
--- a/app/views/projects/show.html.erb
+++ b/app/views/projects/show.html.erb
@@ -4,6 +4,7 @@
data-projects-project-team-id-value="<%= @project.team.id %>"
data-projects-project-team-name-value="<%= @project.team.name %>"
data-projects-project-source-value="<%= @project.source.to_json %>"
+ data-projects-project-defra-hedgerow-permission-value="<%= @project.defra_hedgerow_permission %>"
data-projects-back-button-path-value="<%= team_projects_path(@project.team) %>"
data-projects-db-models-value="<%= render partial: "defs", formats: [:json] %>"
>
diff --git a/db/migrate/20240611095407_create_permissions.rb b/db/migrate/20240611095407_create_permissions.rb
new file mode 100644
index 00000000..33c6f078
--- /dev/null
+++ b/db/migrate/20240611095407_create_permissions.rb
@@ -0,0 +1,9 @@
+class CreatePermissions < ActiveRecord::Migration[6.1]
+ def change
+ create_table :permissions do |t|
+ t.string :name
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20240611095437_create_team_permissions.rb b/db/migrate/20240611095437_create_team_permissions.rb
new file mode 100644
index 00000000..427ab8a3
--- /dev/null
+++ b/db/migrate/20240611095437_create_team_permissions.rb
@@ -0,0 +1,11 @@
+class CreateTeamPermissions < ActiveRecord::Migration[6.1]
+ def change
+ create_table :team_permissions do |t|
+ t.references :team, null: false, foreign_key: true
+ t.references :permission, null: false, foreign_key: true
+ t.boolean :enabled
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20240611101037_add_cascade_delete_to_team_permissions.rb b/db/migrate/20240611101037_add_cascade_delete_to_team_permissions.rb
new file mode 100644
index 00000000..60595bde
--- /dev/null
+++ b/db/migrate/20240611101037_add_cascade_delete_to_team_permissions.rb
@@ -0,0 +1,9 @@
+class AddCascadeDeleteToTeamPermissions < ActiveRecord::Migration[6.1]
+ def change
+ remove_foreign_key :team_permissions, :permissions
+ add_foreign_key :team_permissions, :permissions, on_delete: :cascade
+
+ remove_foreign_key :team_permissions, :teams
+ add_foreign_key :team_permissions, :teams, on_delete: :cascade
+ end
+end
diff --git a/db/migrate/20240611101639_add_hedgerow_permission.rb b/db/migrate/20240611101639_add_hedgerow_permission.rb
new file mode 100644
index 00000000..45dbc3f7
--- /dev/null
+++ b/db/migrate/20240611101639_add_hedgerow_permission.rb
@@ -0,0 +1,21 @@
+class AddHedgerowPermission < ActiveRecord::Migration[6.1]
+
+ def up
+ permission = Permission.find_or_create_by(name: 'defra_hedgerow')
+
+ Team.all.each do |team|
+ TeamPermission.find_or_create_by(team: team, permission: permission, enabled: false)
+ end
+
+ end
+
+ def down
+ permission = Permission.find_by(name: 'defra_hedgerow')
+
+ if permission
+ TeamPermission.where(permission: permission).destroy_all
+ permission.destroy
+ end
+ end
+
+end
diff --git a/db/schema.rb b/db/schema.rb
index 06655d77..78396691 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2024_05_21_101847) do
+ActiveRecord::Schema.define(version: 2024_06_11_101639) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -216,6 +216,12 @@
t.index ["region_id"], name: "index_overlays_on_region_id"
end
+ create_table "permissions", force: :cascade do |t|
+ t.string "name"
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ end
+
create_table "projects", force: :cascade do |t|
t.bigint "team_id", null: false
t.jsonb "source", default: {}, null: false
@@ -232,6 +238,16 @@
t.index ["team_id"], name: "index_regions_on_team_id"
end
+ create_table "team_permissions", force: :cascade do |t|
+ t.bigint "team_id", null: false
+ t.bigint "permission_id", null: false
+ t.boolean "enabled"
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ t.index ["permission_id"], name: "index_team_permissions_on_permission_id"
+ t.index ["team_id"], name: "index_team_permissions_on_team_id"
+ end
+
create_table "teams", force: :cascade do |t|
t.string "name"
t.datetime "created_at", precision: 6, null: false
@@ -284,5 +300,7 @@
add_foreign_key "overlays", "regions"
add_foreign_key "projects", "teams"
add_foreign_key "regions", "teams"
+ add_foreign_key "team_permissions", "permissions", on_delete: :cascade
+ add_foreign_key "team_permissions", "teams", on_delete: :cascade
add_foreign_key "training_data_downloads", "labelling_groups"
end
diff --git a/test/fixtures/permissions.yml b/test/fixtures/permissions.yml
new file mode 100644
index 00000000..7d412240
--- /dev/null
+++ b/test/fixtures/permissions.yml
@@ -0,0 +1,7 @@
+# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+one:
+ name: MyString
+
+two:
+ name: MyString
diff --git a/test/fixtures/team_permissions.yml b/test/fixtures/team_permissions.yml
new file mode 100644
index 00000000..2084ca09
--- /dev/null
+++ b/test/fixtures/team_permissions.yml
@@ -0,0 +1,11 @@
+# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+one:
+ team: one
+ permission: one
+ enabled: false
+
+two:
+ team: two
+ permission: two
+ enabled: false
diff --git a/test/models/permission_test.rb b/test/models/permission_test.rb
new file mode 100644
index 00000000..d823d7f5
--- /dev/null
+++ b/test/models/permission_test.rb
@@ -0,0 +1,7 @@
+require "test_helper"
+
+class PermissionTest < ActiveSupport::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end
diff --git a/test/models/team_permission_test.rb b/test/models/team_permission_test.rb
new file mode 100644
index 00000000..85d75295
--- /dev/null
+++ b/test/models/team_permission_test.rb
@@ -0,0 +1,7 @@
+require "test_helper"
+
+class TeamPermissionTest < ActiveSupport::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end