artserve 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ee35cbcad011ec2401a4f4f2721085fb446f59a90b4bd8d44639fa9664fc1aa3
4
- data.tar.gz: c463a6104dce4ce3fcc4ddbd15ad3aeb9e49337c8da0f02e89e26b3e63bb078d
3
+ metadata.gz: 60a1c24aca28429732b6b6b90a2008b3abd8d56e5e7849175c7a16701b7dbdbf
4
+ data.tar.gz: f8c745e129b3d4b421f5681056b5dd6d8507244ef69c5f40cdee56a0e4ae4b05
5
5
  SHA512:
6
- metadata.gz: a2b2d6e2bd48a9a8a6cc8eebc8d480462917fe1e677172077f55f94851537f1a28f47f3e356f39edf8a66117609744c87dd69576038359ec3ddd88887d420d43
7
- data.tar.gz: ca5a7940eeaac3a47baa5213090dc67f5acfdf8ca8204b3ea0594d4a0617fa19d2ebe0578a824c54f3d0a680529a9b391a3dbec66a5ff4521ab139793414e78d
6
+ metadata.gz: a48553cf45dff026e510acfa29aaa7af1d10623807233ca10a2bc1b5cb3b0ff9320c30194b0efce64d20df7ec27cb39ec6da9e08c2277b8e0b471ec9b619ab7d
7
+ data.tar.gz: 6bc157bfdab797976939144dde837de49aa98d9b3dfb9c0ad180f33e1ae44986981e184715bb8841c1f4ea361b4904f5ac2208346d963eff8fefe1f5a7f3402b
data/Manifest.txt CHANGED
@@ -4,6 +4,7 @@ README.md
4
4
  Rakefile
5
5
  bin/artserve
6
6
  lib/artserve.rb
7
+ lib/artserve/public/artbase.js
7
8
  lib/artserve/public/style.css
8
9
  lib/artserve/service.rb
9
10
  lib/artserve/version.rb
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Artserve
2
2
 
3
3
 
4
- artserve - serve up single-file SQLite artbase dbs to query metadata and images with SQL and more"
4
+ artserve - serve up single-file SQLite artbase dbs to query metadata and images with SQL and more
5
5
 
6
6
 
7
7
  * home :: [github.com/pixelartexchange/artbase](https://github.com/pixelartexchange/artbase)
@@ -13,7 +13,36 @@ artserve - serve up single-file SQLite artbase dbs to query metadata and images
13
13
 
14
14
  ## Command-Line Tool
15
15
 
16
- To be done
16
+ Use the command line tool named - surprise, surpirse - `artserve`
17
+ to run a zero-config / out-of-the-box artbase server that lets
18
+ you query entire collections in single sqlite database (metadata & images) with a "serverless" web page.
19
+ Type:
20
+
21
+ $ artserve # defaults to ./artbase.db
22
+
23
+ That will start-up a (local loopback) web server running on port 3000.
24
+ Open-up up the index page in your browser to get started e.g. <http://localhost:3000/>.
25
+
26
+ That's it.
27
+
28
+
29
+
30
+ **`artbase.db` Options**
31
+
32
+ If you pass in a directory to artserve
33
+ the machinery will look for an `artbase.db` in the directory e.g.
34
+
35
+ $ artserve moonbirds # defaults to ./moonbirds/artbase.db
36
+ $ artserve goblintown # defaults to ./goblintown/artbase.db
37
+ # ...
38
+
39
+ If you pass in a file to artserve
40
+ the machinery will use the bespoke name & path to look for the sqlite database e.g.
41
+
42
+ $ artserve punkbase.db
43
+ $ artserve moonbirdbase.db
44
+ # ...
45
+
17
46
 
18
47
 
19
48
 
@@ -0,0 +1,160 @@
1
+
2
+
3
+ // Load a script from given `url`
4
+ function loadScript(url) {
5
+ return new Promise(function (resolve, reject) {
6
+ const script = document.createElement('script');
7
+ script.src = url;
8
+
9
+ script.addEventListener('load', function () {
10
+ // The script is loaded completely
11
+ resolve(true);
12
+ });
13
+
14
+ document.head.appendChild(script);
15
+ });
16
+ }
17
+
18
+
19
+
20
+
21
+ class Artbase {
22
+
23
+
24
+ async init( options={} ) {
25
+
26
+ const DEFAULTS = {
27
+ database: "artbase.db",
28
+ };
29
+
30
+ this.settings = Object.assign( {}, DEFAULTS, options );
31
+
32
+ console.log( "options:" );
33
+ console.log( options );
34
+ console.log( "settings:" );
35
+ console.log( this.settings );
36
+
37
+
38
+ // todo/fix:
39
+ // bundle "offline" with artserve - why? why not?
40
+
41
+ console.log( "fetching sql.js..." );
42
+ await loadScript( 'https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.6.1/sql-wasm.js' );
43
+ console.log( "done fetching sql.js" );
44
+
45
+
46
+ const sqlPromise = initSqlJs({
47
+ locateFile: file => "https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.6.1/sql-wasm.wasm"
48
+ });
49
+
50
+
51
+ const dataPromise = fetch( this.settings.database ).then(res => res.arrayBuffer());
52
+ const [SQL, buf] = await Promise.all([sqlPromise, dataPromise])
53
+ this.db = new SQL.Database(new Uint8Array(buf));
54
+ }
55
+
56
+
57
+
58
+ _build_query() {
59
+ // get select query as string
60
+ let select = document.querySelector("#select").value
61
+ if (select.length === 0) {
62
+ select = "*"
63
+ } else {
64
+ if (select !== "*") {
65
+ if (!/.*image.*/.test(select)) {
66
+ select = select + ", image"
67
+ }
68
+ if (!/.*id.*/.test(select)) {
69
+ select = select + ", id"
70
+ }
71
+ }
72
+ }
73
+ let where = document.querySelector("#where").value
74
+ let limit = parseInt(document.querySelector("#limit").value)
75
+
76
+ let sql = `SELECT ${select} FROM metadata`
77
+ if (where.length > 0) {
78
+ sql += ` WHERE ${where}`
79
+ }
80
+ if (limit > 0) {
81
+ sql += ` LIMIT ${limit}`
82
+ } else {
83
+ sql += " LIMIT 200"
84
+ }
85
+
86
+ return sql
87
+ }
88
+
89
+
90
+ _build_records( result ) {
91
+ let records = []
92
+
93
+ if( result.length !== 0) {
94
+ let columns = []
95
+
96
+ for(let column of result[0].columns) {
97
+ columns.push(column)
98
+ }
99
+
100
+ for(let i=0; i<result[0].values.length; i++) {
101
+ let values = result[0].values[i];
102
+ let o = { attributes: {} }
103
+ for(let j=0; j<columns.length; j++) {
104
+ let column = columns[j]
105
+ // add id to "hidden" system properties / attributes - why? why not?
106
+ if (["id",
107
+ "image",
108
+ "created_at",
109
+ "updated_at" ].includes(column)) {
110
+ o[column] = values[j]
111
+ } else {
112
+ // only add non-null attributes - why? why not?
113
+ if( values[j] != null )
114
+ o.attributes[column] = values[j]
115
+ }
116
+ }
117
+ records.push(o)
118
+ }
119
+ }
120
+ return records
121
+ }
122
+
123
+
124
+ // change to update() or such - why? why not?
125
+ next() {
126
+ const sql = this._build_query()
127
+ const result = this.db.exec( sql )
128
+ const records = this._build_records( result )
129
+
130
+ let html = ""
131
+ if (records.length === 0) {
132
+ html = "No results"
133
+ } else {
134
+ console.log(records)
135
+ html = records.map((rec) => {
136
+ let attributes = rec.attributes
137
+ let keys = Object.keys(attributes)
138
+ // note: use "" for attribute quotes
139
+ // to allow single-quotes in values e.g. Wizard's Hat etc.
140
+ let table = keys.map((key) => {
141
+ return `<tr class="row" data-key="${key}"
142
+ data-val="${attributes[key]}">
143
+ <td>${key}</td>
144
+ <td>${attributes[key]}</td>
145
+ </tr>`
146
+ }).join("")
147
+
148
+ let img = rec.image
149
+
150
+ return `<div class="item">
151
+ <img src="${img}">
152
+ <table>${table}</table>
153
+ </div>`
154
+ }).join("")
155
+ }
156
+
157
+ document.querySelector(".container").innerHTML = html
158
+ }
159
+ }
160
+
@@ -1,18 +1,137 @@
1
1
  body {
2
2
  font-family: sans-serif;
3
- color: #333333;
4
- }
5
-
6
- a, a:visited {
3
+ margin: 0;
4
+ background: rgba(0,0,255,0.04);
5
+ }
6
+ nav {
7
+ text-align: center;
8
+ padding: 40px 0 20px 0;
9
+ color: black;
10
+ opacity: 0.9;
11
+ }
12
+ .sub {
13
+ font-size: 14px;
14
+ }
15
+ input[type=text] {
16
+ width: 100%;
17
+ padding: 5px;
18
+ box-sizing: border-box;
19
+ background: white;
20
+ border: 1px solid rgba(0,0,0,0.1);
21
+ }
22
+ textarea {
23
+ width: 100%;
24
+ height: 50px;
25
+ background: white;
26
+ border: 1px solid rgba(0,0,0,0.1);
27
+ padding: 5px;
28
+ box-sizing: border-box;
29
+ }
30
+ nav a {
31
+ font-family: sans-serif;
32
+ /*
33
+ letter-spacing: -3px;
34
+ */
7
35
  text-decoration: none;
8
- /* color: maroon; */
9
- }
36
+ font-size: 40px;
37
+ font-weight: bold;
38
+ display: inline-block;
39
+ margin: 10px;
40
+ color: black;
41
+ }
42
+ nav td:nth-child(1) {
43
+ width: 100px;
44
+ font-weight: bold;
45
+ text-align: right;
46
+ vertical-align: middle;
47
+ }
48
+ nav table {
49
+ max-width: 800px;
50
+ margin: 0 auto;
51
+ width: 90%;
52
+ }
53
+ nav td {
54
+ background: none;
55
+ color: black;
56
+ }
57
+
58
+
59
+ .container {
60
+ display: flex;
61
+ flex-wrap: wrap;
62
+ justify-content: center;
63
+ padding: 20px 0;
64
+ }
65
+ .item {
66
+ width: 200px;
67
+ padding: 5px;
68
+ box-sizing: border-box;
69
+ }
70
+ .item img {
71
+ width: 100%;
72
+ image-rendering: pixelated;
73
+ /* to phunk or not phunk? */
74
+ /* transform: scale(-1,1); */
75
+ }
76
+ table {
77
+ table-layout: fixed;
78
+ width: 100%;
79
+ border-spacing: 0;
80
+ }
81
+ td {
82
+ background: white;
83
+ vertical-align: top;
84
+ word-wrap:break-word;
85
+ padding: 7px;
86
+ box-sizing: border-box;
87
+ font-size: 13px;
88
+ cursor: pointer;
89
+ }
90
+ .container td:nth-child(1) {
91
+ font-weight: bold;
92
+ }
93
+ .container td {
94
+ border-bottom: 2px solid rgba(0,0,255,0.04);
95
+ }
96
+ .container tr:hover td {
97
+ background: lavender;
98
+ }
99
+
10
100
 
11
- a:hover {
12
- text-decoration: underline;
13
- /* color: maroon; */
14
- }
15
101
 
102
+ #query {
103
+ margin-top: 5px;
104
+ background: royalblue;
105
+ cursor: pointer;
106
+ padding: 10px;
107
+ max-width: 800px;
108
+ width: 90%;
109
+ box-sizing: border-box;
110
+ color: white;
111
+ border-radius: 4px;
112
+ border:none;
113
+ font-size: 20px;
114
+ font-family: sans-serif;
115
+ }
116
+ .btn {
117
+ font-family: sans-serif;
118
+ font-size: 14px;
119
+ font-style: normal;
120
+ letter-spacing: 0;
121
+ background: rgba(0,0,0,0.9);
122
+ color: white;
123
+ padding: 5px 10px;
124
+ margin: 20px 0;
125
+ border-radius: 2px;
126
+ }
127
+
128
+ .loader {
129
+ padding: 100px;
130
+ font-size: 14px;
131
+ opacity: 0.9;
132
+ font-weight: bold;
133
+ text-transform: uppercase;
134
+ }
16
135
 
17
136
 
18
137
  /** version block **********/
@@ -1,7 +1,19 @@
1
1
 
2
2
  # todo/check: find a better name?
3
3
  class Artserve < Sinatra::Base
4
+
5
+ get '/artbase.db' do
6
+ path = settings.artbase
7
+ puts " serving sqlite database as (binary) blob >#{path}<..."
8
+ headers( 'Content-Type' => "application/octet-stream" )
9
+
10
+ blob = File.open( path, 'rb' ) { |f| f.read }
11
+ puts " #{blob.size} byte(s)"
12
+ blob
13
+ end
14
+
4
15
  get '/' do
5
16
  erb :index
6
17
  end
18
+
7
19
  end # class ProfilepicService
@@ -3,8 +3,8 @@ module Artbase
3
3
  module Module
4
4
  module Artserve
5
5
  MAJOR = 0
6
- MINOR = 0
7
- PATCH = 1
6
+ MINOR = 1
7
+ PATCH = 0
8
8
  VERSION = [MAJOR,MINOR,PATCH].join('.')
9
9
 
10
10
  def self.version
@@ -1,6 +1,75 @@
1
1
 
2
2
 
3
- <h1>Artserve</h1>
4
3
 
5
- <p>Hello.
6
- </p>
4
+ <nav>
5
+ <a href="/"><%= settings.artbase %></a>
6
+ <div class='sub'>Yes, you can! Query the entire
7
+ art collection (metadata & images)
8
+ in a single sqlite database (file)
9
+ with a "serverless" web page.
10
+ </div>
11
+ <div>
12
+ <a class='btn' href="/artbase.db">Download</a>
13
+ <a class='btn' href="https://old.reddit.com/r/DIYPunkArt/">Questions? Comments?</a>
14
+ </div>
15
+ <table>
16
+ <tr>
17
+ <td>SELECT</td>
18
+ <td><input id='select' type='text' placeholder='select columns' value='*'></td>
19
+ </tr>
20
+ <tr>
21
+ <td>FROM</td>
22
+ <td>metadata</td>
23
+ </tr>
24
+ <tr>
25
+ <td>WHERE</td>
26
+ <td><textarea id='where' placeholder='where condition'></textarea></td>
27
+ </tr>
28
+ <tr>
29
+ <td>LIMIT</td>
30
+ <td><input id='limit' type='text' placeholder='limmit' value=200></td>
31
+ </tr>
32
+ </table>
33
+ <button id='query'>Query</button>
34
+ </nav>
35
+
36
+
37
+ <div class='container'>
38
+ <div class='loader'>
39
+ <div class='loading dots'></div>
40
+ <br>
41
+ <div>Loading ...</div>
42
+ </div>
43
+ </div>
44
+
45
+
46
+
47
+
48
+ <script>
49
+ document.addEventListener("DOMContentLoaded", async () => {
50
+ const artbase = new Artbase()
51
+ await artbase.init()
52
+
53
+ artbase.next()
54
+
55
+
56
+ document.querySelector("#query").addEventListener("click", () => {
57
+ artbase.next()
58
+ })
59
+
60
+ document.querySelector(".container").addEventListener("click", (e) => {
61
+ let target = (e.target.className === "row" ? e.target : (e.target.closest(".row") ? e.target.closest(".row") : null))
62
+ if (target) {
63
+ let key = target.getAttribute('data-key')
64
+ let val = target.getAttribute('data-val')
65
+ let where = document.querySelector("#where").value
66
+ if (where.trim().length > 0) {
67
+ document.querySelector("#where").value = `${where} AND\n${key} = "${val}"`
68
+ } else {
69
+ document.querySelector("#where").value = `${key} = "${val}"`
70
+ }
71
+ artbase.next()
72
+ }
73
+ })
74
+ })
75
+ </script>
@@ -3,9 +3,10 @@
3
3
  <head>
4
4
  <meta charset='utf-8'>
5
5
 
6
- <title>Artserve</title>
6
+ <title>Artbase</title>
7
7
 
8
8
  <link href="<%= url('/style.css') %>" rel='stylesheet'>
9
+ <script src="/artbase.js"></script>
9
10
  </head>
10
11
  <body>
11
12
 
data/lib/artserve.rb CHANGED
@@ -10,9 +10,22 @@ require_relative 'artserve/service'
10
10
 
11
11
 
12
12
 
13
- class Artserve
14
- def self.main
15
- puts 'hello from main'
13
+ class Artserve ## note: Artserve is Sinatra::Base
14
+ def self.main( argv=ARGV )
15
+ puts 'hello from main with args:'
16
+ pp argv
17
+
18
+ path = argv[0] || './artbase.db'
19
+
20
+ ## if passed in a directory, auto-add /artbase.db for now - why? why not
21
+ path += "/artbase.db" if Dir.exist?( path )
22
+
23
+
24
+ puts " using database: >#{path}<"
25
+
26
+ ## note: let's you use latter settings.artbase (resulting in path)
27
+ self.set( :artbase, path )
28
+
16
29
 
17
30
  #####
18
31
  # fix/todo:
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: artserve
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gerald Bauer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-09-19 00:00:00.000000000 Z
11
+ date: 2022-09-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sinatra
@@ -89,6 +89,7 @@ files:
89
89
  - Rakefile
90
90
  - bin/artserve
91
91
  - lib/artserve.rb
92
+ - lib/artserve/public/artbase.js
92
93
  - lib/artserve/public/style.css
93
94
  - lib/artserve/service.rb
94
95
  - lib/artserve/version.rb