DataInventory

chore: add elements

1/15/2020 10:29:11 AM

Details

diff --git a/app/Controllers/Http/ElementController.js b/app/Controllers/Http/ElementController.js
new file mode 100644
index 0000000..beb2c94
--- /dev/null
+++ b/app/Controllers/Http/ElementController.js
@@ -0,0 +1,130 @@
+'use strict'
+
+/** @typedef {import('@adonisjs/framework/src/Request')} Request */
+/** @typedef {import('@adonisjs/framework/src/Response')} Response */
+/** @typedef {import('@adonisjs/framework/src/View')} View */
+const Element = use('App/Models/Element')
+const Theme = use('App/Models/Theme')
+const { validate } = use('Validator')
+
+/**
+ * Resourceful controller for interacting with elements
+ */
+class ElementController {
+  /**
+   * Show a list of all elements.
+   * GET elements
+   *
+   * @param {object} ctx
+   * @param {Request} ctx.request
+   * @param {Response} ctx.response
+   * @param {View} ctx.view
+   */
+  async index ({ request, response, view }) {
+    const elements = await Element.query().with('theme').orderBy('name', 'asc').fetch()
+    return view.render('elements.index', {
+      elements: elements.toJSON()
+    })
+  }
+
+  /**
+   * Render a form to be used for creating a new element.
+   * GET elements/create
+   *
+   * @param {object} ctx
+   * @param {Request} ctx.request
+   * @param {Response} ctx.response
+   * @param {View} ctx.view
+   */
+  async create ({ request, response, view }) {
+    const themes = await Theme.all()
+    return view.render('elements.create', {
+      themes: themes.toJSON()
+    })
+  }
+
+  /**
+   * Create/save a new element.
+   * POST elements
+   *
+   * @param {object} ctx
+   * @param {Request} ctx.request
+   * @param {Response} ctx.response
+   */
+  async store ({ request, response }) {
+    const data = request.only(['name', 'theme_id', 'priority', 'refresh_rate'])
+
+    const validation = await validate(request.all(), {
+      name: 'required|max:255',
+      theme_id: 'required',
+      priority: 'required',
+      refresh_rate: 'required'
+    }, {
+      'name.required': `Theme name is required`,
+      'theme_id.required': `Theme is required`,
+      'priority.required': `Priority is required`,
+      'refresh_rate.required': `Refresh rate is required`,
+    })
+
+    if(validation.fails()) {
+      session.withErrors(validation.messages()).flashAll()
+      return response.redirect('back')
+    }
+
+    await Element.create(data)
+
+    return response.redirect('/elements')
+  }
+
+  /**
+   * Display a single element.
+   * GET elements/:id
+   *
+   * @param {object} ctx
+   * @param {Request} ctx.request
+   * @param {Response} ctx.response
+   * @param {View} ctx.view
+   */
+  async show ({ params, request, response, view }) {
+  }
+
+  /**
+   * Render a form to update an existing element.
+   * GET elements/:id/edit
+   *
+   * @param {object} ctx
+   * @param {Request} ctx.request
+   * @param {Response} ctx.response
+   * @param {View} ctx.view
+   */
+  async edit ({ params, request, response, view }) {
+  }
+
+  /**
+   * Update element details.
+   * PUT or PATCH elements/:id
+   *
+   * @param {object} ctx
+   * @param {Request} ctx.request
+   * @param {Response} ctx.response
+   */
+  async update ({ params, request, response }) {
+  }
+
+  /**
+   * Delete a element with id.
+   * DELETE elements/:id
+   *
+   * @param {object} ctx
+   * @param {Request} ctx.request
+   * @param {Response} ctx.response
+   */
+  async destroy ({ params, request, response }) {
+    const element = await Element.findOrFail(params.id)
+    await element.delete()
+
+    return response.redirect('/elements')
+  }
+}
+
+module.exports = ElementController
diff --git a/app/Controllers/Http/ThemeController.js b/app/Controllers/Http/ThemeController.js
new file mode 100644
index 0000000..ed8f288
--- /dev/null
+++ b/app/Controllers/Http/ThemeController.js
@@ -0,0 +1,124 @@
+'use strict'
+
+/** @typedef {import('@adonisjs/framework/src/Request')} Request */
+/** @typedef {import('@adonisjs/framework/src/Response')} Response */
+/** @typedef {import('@adonisjs/framework/src/View')} View */
+
+const Theme = use('App/Models/Theme')
+const { validate } = use('Validator')
+
+/**
+ * Resourceful controller for interacting with themes
+ */
+class ThemeController {
+  /**
+   * Show a list of all themes.
+   * GET themes
+   *
+   * @param {object} ctx
+   * @param {Request} ctx.request
+   * @param {Response} ctx.response
+   * @param {View} ctx.view
+   */
+  async index ({ request, response, view }) {
+    const themes = await Theme.query().orderBy('name', 'asc').fetch()
+  
+    return view.render('themes.index', {
+      themes: themes.toJSON()
+    })
+  }
+
+  /**
+   * Render a form to be used for creating a new theme.
+   * GET themes/create
+   *
+   * @param {object} ctx
+   * @param {Request} ctx.request
+   * @param {Response} ctx.response
+   * @param {View} ctx.view
+   */
+  async create ({ request, response, view }) {
+    return view.render('themes.create')
+  }
+
+  /**
+   * Create/save a new theme.
+   * POST themes
+   *
+   * @param {object} ctx
+   * @param {Request} ctx.request
+   * @param {Response} ctx.response
+   */
+  async store ({ session, request, response }) { 
+
+    const data = request.only(['name'])
+
+    const validation = await validate(request.all(), {
+      name: 'required|max:255',
+    }, {
+      'name.required': `Theme name is required`,
+    })
+
+    if(validation.fails()) {
+      session.withErrors(validation.messages()).flashAll()
+      return response.redirect('back')
+    }
+
+    await Theme.create(data)
+
+    return response.redirect('/themes')
+
+  }
+
+  /**
+   * Display a single theme.
+   * GET themes/:id
+   *
+   * @param {object} ctx
+   * @param {Request} ctx.request
+   * @param {Response} ctx.response
+   * @param {View} ctx.view
+   */
+  async show ({ params, request, response, view }) {
+  }
+
+  /**
+   * Render a form to update an existing theme.
+   * GET themes/:id/edit
+   *
+   * @param {object} ctx
+   * @param {Request} ctx.request
+   * @param {Response} ctx.response
+   * @param {View} ctx.view
+   */
+  async edit ({ params, request, response, view }) {
+  }
+
+  /**
+   * Update theme details.
+   * PUT or PATCH themes/:id
+   *
+   * @param {object} ctx
+   * @param {Request} ctx.request
+   * @param {Response} ctx.response
+   */
+  async update ({ params, request, response }) {
+  }
+
+  /**
+   * Delete a theme with id.
+   * DELETE themes/:id
+   *
+   * @param {object} ctx
+   * @param {Request} ctx.request
+   * @param {Response} ctx.response
+   */
+  async destroy ({ params, request, response }) {
+    const theme = await Theme.findOrFail(params.id)
+    await theme.delete()
+
+    return response.redirect('/themes')
+  }
+}
+
+module.exports = ThemeController
diff --git a/app/Models/Element.js b/app/Models/Element.js
new file mode 100644
index 0000000..0729410
--- /dev/null
+++ b/app/Models/Element.js
@@ -0,0 +1,14 @@
+'use strict'
+
+/** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */
+const Model = use('Model')
+
+class Element extends Model {
+
+  theme() {
+    return this.belongsTo('App/Models/Theme')
+  }
+
+}
+
+module.exports = Element

app/Models/Theme.js 14(+14 -0)

diff --git a/app/Models/Theme.js b/app/Models/Theme.js
new file mode 100644
index 0000000..3a63cae
--- /dev/null
+++ b/app/Models/Theme.js
@@ -0,0 +1,14 @@
+'use strict'
+
+/** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */
+const Model = use('Model')
+
+class Theme extends Model {
+
+  elements() {
+    return this.hadMany('App/Models/Element')
+  }
+
+}
+
+module.exports = Theme
diff --git a/database/migrations/1575321261885_themes_schema.js b/database/migrations/1575321261885_themes_schema.js
index bdc0da5..14b9bc6 100644
--- a/database/migrations/1575321261885_themes_schema.js
+++ b/database/migrations/1575321261885_themes_schema.js
@@ -8,6 +8,7 @@ class ThemesSchema extends Schema {
     this.create('themes', (table) => {
       table.increments('id')
       table.string('name').notNullable().unique()
+      table.timestamps()
     })
   }
 
diff --git a/database/migrations/1575321574938_elements_schema.js b/database/migrations/1575321574938_elements_schema.js
index a7f54f7..4043b49 100644
--- a/database/migrations/1575321574938_elements_schema.js
+++ b/database/migrations/1575321574938_elements_schema.js
@@ -11,6 +11,7 @@ class ElementsSchema extends Schema {
       table.integer('theme_id').notNullable().unsigned().references('id').inTable('themes')
       table.integer('priority').nullable().unsigned()
       table.integer('refresh_rate').nullable()
+      table.timestamps()
     })
   }
 
diff --git a/database/migrations/1575322085612_standards_schema.js b/database/migrations/1575322085612_standards_schema.js
index 31b756a..3b76a9c 100644
--- a/database/migrations/1575322085612_standards_schema.js
+++ b/database/migrations/1575322085612_standards_schema.js
@@ -10,8 +10,8 @@ class StandardsSchema extends Schema {
       table.integer('element_id').notNullable().unsigned().references('id').inTable('elements')
       table.string('document').notNullable()
       table.float('version', 2).unsigned().notNullable()
-      table.timestamps()
       table.date('retired').nullable()
+      table.timestamps()
     })
   }
 
diff --git a/database/migrations/1575322481931_organization_types_schema.js b/database/migrations/1575322481931_organization_types_schema.js
index ac46e44..47fce01 100644
--- a/database/migrations/1575322481931_organization_types_schema.js
+++ b/database/migrations/1575322481931_organization_types_schema.js
@@ -8,6 +8,7 @@ class OrganizationTypesSchema extends Schema {
     this.create('organization_types', (table) => {
       table.increments('id')
       table.string('type').notNullable()
+      table.timestamps()
     })
   }
 
diff --git a/database/migrations/1575323434337_elements_stewards_schema.js b/database/migrations/1575323434337_elements_stewards_schema.js
index 1077dd7..07638ea 100644
--- a/database/migrations/1575323434337_elements_stewards_schema.js
+++ b/database/migrations/1575323434337_elements_stewards_schema.js
@@ -8,6 +8,7 @@ class ElementsStewardsSchema extends Schema {
     this.create('elements_stewards', (table) => {
       table.integer('element_id').references('id').inTable('elements')
       table.integer('steward_id').references('id').inTable('stewards')
+      table.timestamps()
     })
   }
 

package.json 1(+1 -0)

diff --git a/package.json b/package.json
index d4743c3..9cb4309 100644
--- a/package.json
+++ b/package.json
@@ -26,6 +26,7 @@
     "@adonisjs/lucid": "^6.1.3",
     "@adonisjs/session": "^1.0.27",
     "@adonisjs/shield": "^1.0.8",
+    "@adonisjs/validator": "^5.0.6",
     "pg": "^7.14.0"
   },
   "devDependencies": {},

public/logo.png 0(+0 -0)

diff --git a/public/logo.png b/public/logo.png
new file mode 100644
index 0000000..64dd6bc
Binary files /dev/null and b/public/logo.png differ
diff --git a/resources/views/elements/create.edge b/resources/views/elements/create.edge
new file mode 100644
index 0000000..0808db7
--- /dev/null
+++ b/resources/views/elements/create.edge
@@ -0,0 +1,53 @@
+@layout('layouts.main')
+
+@section('content')
+
+<form method="POST" action="{{ route('/elements.store') }}">
+  {{ csrfField() }}
+  <div class="field">
+    <label class="label">Element Name</label>
+    <input class="input" type="text" name="name" placeholder="Zoning" value="{{ old('name', '') }}" />
+    {{ elIf('<span class="has-text-danger">$self</span>', getErrorFor('name'), hasErrorFor('name')) }}
+  </div>
+
+  <div class="field">
+    <label class="label">Theme</label>
+    <div class="select">
+      <select name="theme_id">
+        <option>Select dropdown</option>
+        @each(theme in themes)
+          <option value="{{theme.id}}">{{theme.name}}</option>
+        @endeach
+      </select>
+    </div>
+  </div>
+
+  <div class="field">
+    <label class="label">Priority</label>
+    <input class="input" type="text" name="priority" placeholder="1 (lowest) - 3 (highest)" value="{{ old('priority', '') }}" />
+    {{ elIf('<span class="has-text-danger">$self</span>', getErrorFor('priority'), hasErrorFor('priority')) }}
+  </div>
+
+  <div class="field">
+    <label class="label">Refresh Rate (days)</label>
+    <input class="input" type="text" name="refresh_rate" placeholder="90" value="{{ old('refresh_rate', '') }}" />
+    {{ elIf('<span class="has-text-danger">$self</span>', getErrorFor('refresh_rate'), hasErrorFor('refresh_rate')) }}
+  </div>
+
+  <div class="field is-grouped">
+      <div class="control">
+        <button class="button is-link ss-submit" type="submit">Submit</button>
+      </div>
+      <div class="control">
+      <button class="button is-text"><a href="{{ route('/themes.index') }}">Cancel</a></button>
+      </div>
+    </div>
+    <!--
+    <div class="notification is-warning">
+      <button class="delete"></button>
+      Building Snapshots for some items may take a couple minutes.  Please be patient!
+    </div>
+    -->
+</form>
+
+@endsection 
\ No newline at end of file
diff --git a/resources/views/elements/index.edge b/resources/views/elements/index.edge
new file mode 100644
index 0000000..a8a48ae
--- /dev/null
+++ b/resources/views/elements/index.edge
@@ -0,0 +1,44 @@
+@layout('layouts.main')
+
+@section('content')
+
+<section class="box is-full-width">
+  <h3 class="is-size-3">Theme Actions</h3>
+  <ul>
+    <li><a href="{{ route('/elements/create') }}">Add Element</a></li>
+  </ul>
+</section>
+
+<p>There are {{ elements.length }} total elements.</p>
+
+<section class="box">
+  <table class="table">
+    <thead class="thead">
+      <tr>
+        <th>Element Name</th>
+        <th>Theme</th>
+        <th>Priority</th>
+        <th>Refresh Rate</th>
+        <th>Action</th>
+      </tr>
+    </thead>
+    <tbody class="tbody">
+      @each(element in elements)
+      <tr>
+        <td>{{ element.name }}</td>
+        <td>{{ element.theme.name }}</td>
+        <td>{{ element.priority }}</td>
+        <td>{{ element.refresh_rate }}</td>
+        <td>
+          <form action="{{ route('/elements.destroy', { id: element.id }) + '?_method=DELETE'}}" method="post">
+            {{ csrfField() }}
+            <button type="submit" name="button" class="is-small button is-danger" onclick="return confirm('Are you sure you want to delete this element?');">Delete</button>
+          </form>
+        </td>
+      </tr>
+      @endeach
+    </tbody>
+  </table>
+</section>
+
+@endsection
\ No newline at end of file
diff --git a/resources/views/inc/nav.edge b/resources/views/inc/nav.edge
index 2737bf1..9bac3e0 100644
--- a/resources/views/inc/nav.edge
+++ b/resources/views/inc/nav.edge
@@ -1,6 +1,19 @@
-<section class="box">
-  <h1 class="is-size-4">Data Inventory Tracking</h1>
+<section class="content box">
+  <h2 class="is-size-5">Main Links</h2>
+  <ul>
+    <li>Main link...</li>
+  </ul>
 </section>
-<ul>
-  <li>Navigation Link</li>
-</ul>
\ No newline at end of file
+
+<section class="content box">
+  <h2 class="is-size-5">Admin Links</h2>
+  <ul>
+    <li>
+      <a href="{{ route('/themes.index') }}">Themes</a>
+    </li>
+    <li>
+      <a href="{{ route('/elements.index') }}">Elements</a>
+    </li>
+  </ul>
+</section>
+
diff --git a/resources/views/layouts/main.edge b/resources/views/layouts/main.edge
index 9d391bd..e00123c 100644
--- a/resources/views/layouts/main.edge
+++ b/resources/views/layouts/main.edge
@@ -13,12 +13,27 @@
 </head>
 <body class='container'>
   <div class="content">
+    <nav class="navbar" role="navigation" aria-label="main navigation">
+      <div class="navbar-brand">
+        <a class="navbar-item" href="/">
+        <img src="{{ assetsUrl('logo.png') }}"" alt="Bulma: Free, open source, and modern CSS framework based on Flexbox" width="40" height="100%"> Oregon Geospatial Enterprise Office - Data Inventory Tracking
+        </a>
+    
+        <a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false">
+          <span aria-hidden="true"></span>
+          <span aria-hidden="true"></span>
+          <span aria-hidden="true"></span>
+        </a>
+      </div>
+    </nav>
+  </div>
+  <div class="content">
     <div class="columns">
-      <div class="column is-3">
+      <div class="column is-3 container">
         <!-- Navigation Left Column -->
         @include('inc.nav')
       </div>
-      <div class="column is-9">
+      <div class="column is-9 container">
         <!-- Main Right Column -->
         @!section('content')
       </div>
@@ -26,6 +41,6 @@
     @include('inc.footer')
   </div>
 @!section('scripts')
-{{ script('js/main.js') }}
+{{--  {{ script('js/main.js') }}  --}}
 </body>
 </html>
\ No newline at end of file
diff --git a/resources/views/themes/create.edge b/resources/views/themes/create.edge
new file mode 100644
index 0000000..b7999fb
--- /dev/null
+++ b/resources/views/themes/create.edge
@@ -0,0 +1,29 @@
+@layout('layouts.main')
+
+@section('content')
+
+<form method="POST" action="{{ route('/themes.store') }}">
+  {{ csrfField() }}
+  <div class="field">
+    <label class="label">Theme Name</label>
+    <input class="input" type="text" name="name" placeholder="Admin boundaries" value="{{ old('name', '') }}" />
+    {{ elIf('<span class="has-text-danger">$self</span>', getErrorFor('name'), hasErrorFor('name')) }}
+  </div>
+
+  <div class="field is-grouped">
+      <div class="control">
+        <button class="button is-link ss-submit" type="submit">Submit</button>
+      </div>
+      <div class="control">
+      <button class="button is-text"><a href="{{ route('/themes.index') }}">Cancel</a></button>
+      </div>
+    </div>
+    <!--
+    <div class="notification is-warning">
+      <button class="delete"></button>
+      Building Snapshots for some items may take a couple minutes.  Please be patient!
+    </div>
+    -->
+</form>
+
+@endsection 
\ No newline at end of file
diff --git a/resources/views/themes/index.edge b/resources/views/themes/index.edge
new file mode 100644
index 0000000..7d97b00
--- /dev/null
+++ b/resources/views/themes/index.edge
@@ -0,0 +1,38 @@
+@layout('layouts.main')
+
+@section('content')
+
+<section class="box is-full-width">
+  <h3 class="is-size-3">Theme Actions</h3>
+  <ul>
+    <li><a href="{{ route('/themes/create') }}">Add Theme</a></li>
+  </ul>
+</section>
+
+<p>There are {{ themes.length }} total themes.</p>
+
+<section class="box">
+  <table class="table">
+    <thead class="thead">
+      <tr>
+        <th>Theme Name</th>
+        <th>Action</th>
+      </tr>
+    </thead>
+    <tbody class="tbody">
+      @each(theme in themes)
+      <tr>
+        <td>{{ theme.name }}</td>
+        <td>
+          <form action="{{ route('/themes.destroy', { id: theme.id }) + '?_method=DELETE'}}" method="post">
+            {{ csrfField() }}
+            <button type="submit" name="button" class="is-small button is-danger" onclick="return confirm('Are you sure you want to delete this theme?');">Delete</button>
+          </form>
+        </td>
+      </tr>
+      @endeach
+    </tbody>
+  </table>
+</section>
+
+@endsection
\ No newline at end of file

start/app.js 3(+2 -1)

diff --git a/start/app.js b/start/app.js
index e42f331..eb9b807 100644
--- a/start/app.js
+++ b/start/app.js
@@ -18,7 +18,8 @@ const providers = [
   '@adonisjs/cors/providers/CorsProvider',
   '@adonisjs/shield/providers/ShieldProvider',
   '@adonisjs/session/providers/SessionProvider',
-  '@adonisjs/auth/providers/AuthProvider'
+  '@adonisjs/auth/providers/AuthProvider',
+  '@adonisjs/validator/providers/ValidatorProvider'
 ]
 
 /*

start/routes.js 6(+5 -1)

diff --git a/start/routes.js b/start/routes.js
index c6ad938..8fa8e76 100644
--- a/start/routes.js
+++ b/start/routes.js
@@ -17,4 +17,8 @@
 const Route = use('Route')
 
 // Main routes
-Route.on('/').render('index')
\ No newline at end of file
+Route.on('/').render('index')
+
+// Resource Routes (FULL CRUD)
+Route.resource('/themes','ThemeController')
+Route.resource('/elements', 'ElementController')
\ No newline at end of file