Class: WOWSQL::WOWSQLStorage

Inherits:
Object
  • Object
show all
Defined in:
lib/wowsql/storage.rb

Overview

WowSQL Storage Client — PostgreSQL-native file storage.

Files are stored as BYTEA inside each project’s storage schema. No external S3 dependency — everything lives in PostgreSQL.

Examples:

storage = WOWSQL::WOWSQLStorage.new(
  "https://myproject.wowsql.com",
  "wowsql_anon_..."
)
bucket = storage.create_bucket("avatars", public: true)
File.open("photo.jpg", "rb") { |f| storage.upload("avatars", f, path: "users/profile.jpg") }
files = storage.list_files("avatars", prefix: "users/")
url = storage.get_public_url("avatars", "users/profile.jpg")

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(project_url = '', api_key = '', project_slug: '', base_url: '', base_domain: 'wowsqlconnect.com', secure: true, timeout: 60, verify_ssl: true) ⇒ WOWSQLStorage

Returns a new instance of WOWSQLStorage.

Parameters:

  • project_url (String) (defaults to: '')

    Project subdomain or full URL

  • api_key (String) (defaults to: '')

    API key for authentication

  • project_slug (String) (defaults to: '')

    Explicit slug (used with base_url)

  • base_url (String) (defaults to: '')

    Explicit base URL (used with project_slug)

  • base_domain (String) (defaults to: 'wowsqlconnect.com')

    Base domain (default: “wowsqlconnect.com”)

  • secure (Boolean) (defaults to: true)

    Use HTTPS (default: true)

  • timeout (Integer) (defaults to: 60)

    Request timeout in seconds (default: 60)

  • verify_ssl (Boolean) (defaults to: true)

    Verify SSL certificates (default: true)



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/wowsql/storage.rb', line 105

def initialize(project_url = '', api_key = '', project_slug: '', base_url: '',
               base_domain: 'wowsqlconnect.com', secure: true, timeout: 60, verify_ssl: true)
  if !project_slug.empty? && !base_url.empty?
    @base_url = base_url.chomp('/')
    @project_slug = project_slug
  elsif !project_url.empty?
    url = project_url.strip
    if url.start_with?('http://') || url.start_with?('https://')
      @base_url = url.chomp('/')
      @base_url = @base_url.split('/api').first if @base_url.include?('/api')
    else
      protocol = secure ? 'https' : 'http'
      if url.include?(".#{base_domain}") || url.end_with?(base_domain)
        @base_url = "#{protocol}://#{url}"
      else
        @base_url = "#{protocol}://#{url}.#{base_domain}"
      end
    end
    @project_slug = url.split('.').first
                        .sub('https://', '')
                        .sub('http://', '')
  else
    raise ArgumentError, 'Either project_url or (project_slug + base_url) must be provided'
  end

  @timeout = timeout
  @verify_ssl = verify_ssl

  ssl_options = verify_ssl ? {} : { verify: false }

  @conn = Faraday.new(url: @base_url, ssl: ssl_options) do |f|
    f.request :multipart
    f.request :json
    f.response :json
    f.adapter Faraday.default_adapter
    f.options.timeout = timeout
  end

  @conn.headers['Authorization'] = "Bearer #{api_key}"
end

Instance Attribute Details

#base_urlObject (readonly)

Returns the value of attribute base_url.



95
96
97
# File 'lib/wowsql/storage.rb', line 95

def base_url
  @base_url
end

#project_slugObject (readonly)

Returns the value of attribute project_slug.



95
96
97
# File 'lib/wowsql/storage.rb', line 95

def project_slug
  @project_slug
end

Instance Method Details

#closeObject

Close the HTTP connection.



342
343
344
# File 'lib/wowsql/storage.rb', line 342

def close
  @conn.close if @conn.respond_to?(:close)
end

#create_bucket(name, public: false, file_size_limit: nil, allowed_mime_types: nil) ⇒ StorageBucket

Create a new storage bucket.

Parameters:

  • name (String)

    Bucket name

  • public (Boolean) (defaults to: false)

    Whether the bucket is public

  • file_size_limit (Integer, nil) (defaults to: nil)

    Max file size in bytes

  • allowed_mime_types (Array<String>, nil) (defaults to: nil)

    Allowed MIME types

Returns:



155
156
157
158
159
160
161
162
163
# File 'lib/wowsql/storage.rb', line 155

def create_bucket(name, public: false, file_size_limit: nil, allowed_mime_types: nil)
  data = request('POST', "/api/v1/storage/projects/#{@project_slug}/buckets", nil, {
    name: name,
    public: public,
    file_size_limit: file_size_limit,
    allowed_mime_types: allowed_mime_types
  })
  StorageBucket.new(data)
end

#delete_bucket(name) ⇒ Hash

Delete a bucket and all its files.

Parameters:

  • name (String)

    Bucket name

Returns:

  • (Hash)


196
197
198
# File 'lib/wowsql/storage.rb', line 196

def delete_bucket(name)
  request('DELETE', "/api/v1/storage/projects/#{@project_slug}/buckets/#{name}", nil, nil)
end

#delete_file(bucket_name, file_path) ⇒ Hash

Delete a file from a bucket.

Parameters:

  • bucket_name (String)

    Bucket name

  • file_path (String)

    File path within bucket

Returns:

  • (Hash)


309
310
311
# File 'lib/wowsql/storage.rb', line 309

def delete_file(bucket_name, file_path)
  request('DELETE', "/api/v1/storage/projects/#{@project_slug}/files/#{bucket_name}/#{file_path}", nil, nil)
end

#download(bucket_name, file_path) ⇒ String

Download a file and return its binary contents.

Parameters:

  • bucket_name (String)

    Bucket name

  • file_path (String)

    File path within bucket

Returns:

  • (String)

    Binary file contents



278
279
280
281
282
283
284
285
286
287
288
# File 'lib/wowsql/storage.rb', line 278

def download(bucket_name, file_path)
  url = "/api/v1/storage/projects/#{@project_slug}/files/#{bucket_name}/#{file_path}"

  response = @conn.get(url)
  if response.status >= 400
    handle_error(response)
  end
  response.body
rescue Faraday::Error => e
  raise StorageError.new("Download failed: #{e.message}")
end

#download_to_file(bucket_name, file_path, local_path) ⇒ String

Download a file and save it to a local path.

Parameters:

  • bucket_name (String)

    Bucket name

  • file_path (String)

    File path within bucket

  • local_path (String)

    Local destination path

Returns:

  • (String)

    The local path



296
297
298
299
300
301
302
# File 'lib/wowsql/storage.rb', line 296

def download_to_file(bucket_name, file_path, local_path)
  content = download(bucket_name, file_path)
  dir = File.dirname(local_path)
  FileUtils.mkdir_p(dir) unless dir == '.'
  File.open(local_path, 'wb') { |f| f.write(content) }
  local_path
end

#get_bucket(name) ⇒ StorageBucket

Get a specific bucket by name.

Parameters:

  • name (String)

    Bucket name

Returns:



177
178
179
180
# File 'lib/wowsql/storage.rb', line 177

def get_bucket(name)
  data = request('GET', "/api/v1/storage/projects/#{@project_slug}/buckets/#{name}", nil, nil)
  StorageBucket.new(data)
end

#get_public_url(bucket_name, file_path) ⇒ String

Get the public URL for a file in a public bucket (no auth required).

Parameters:

  • bucket_name (String)

    Bucket name

  • file_path (String)

    File path within bucket

Returns:

  • (String)


320
321
322
# File 'lib/wowsql/storage.rb', line 320

def get_public_url(bucket_name, file_path)
  "#{@base_url}/api/v1/storage/projects/#{@project_slug}/files/#{bucket_name}/#{file_path}"
end

#get_quota(force_refresh: false) ⇒ StorageQuota

Get storage quota (alias for get_stats).

Returns:



335
336
337
# File 'lib/wowsql/storage.rb', line 335

def get_quota(force_refresh: false)
  get_stats
end

#get_statsStorageQuota

Get storage statistics for the project.

Returns:



327
328
329
330
# File 'lib/wowsql/storage.rb', line 327

def get_stats
  data = request('GET', "/api/v1/storage/projects/#{@project_slug}/stats", nil, nil)
  StorageQuota.new(data)
end

#list_bucketsArray<StorageBucket>

List all buckets in the project.

Returns:



168
169
170
171
# File 'lib/wowsql/storage.rb', line 168

def list_buckets
  data = request('GET', "/api/v1/storage/projects/#{@project_slug}/buckets", nil, nil)
  Array(data).map { |b| StorageBucket.new(b) }
end

#list_files(bucket_name, prefix: nil, limit: 100, offset: 0) ⇒ Array<StorageFile>

List files in a bucket.

Parameters:

  • bucket_name (String)

    Bucket name

  • prefix (String, nil) (defaults to: nil)

    Filter by prefix/folder

  • limit (Integer) (defaults to: 100)

    Maximum files to return

  • offset (Integer) (defaults to: 0)

    Offset for pagination

Returns:



264
265
266
267
268
269
270
271
# File 'lib/wowsql/storage.rb', line 264

def list_files(bucket_name, prefix: nil, limit: 100, offset: 0)
  params = { 'limit' => limit, 'offset' => offset }
  params['prefix'] = prefix if prefix

  data = request('GET', "/api/v1/storage/projects/#{@project_slug}/buckets/#{bucket_name}/files", params, nil)
  items = data.is_a?(Array) ? data : (data['files'] || data['data'] || [])
  items.map { |f| StorageFile.new(f) }
end

#to_sObject Also known as: inspect



346
347
348
# File 'lib/wowsql/storage.rb', line 346

def to_s
  "WOWSQLStorage(project=#{@project_slug.inspect})"
end

#update_bucket(name, **options) ⇒ StorageBucket

Update bucket settings.

Parameters:

  • name (String)

    Bucket name

  • options (Hash)

    Settings to update

Returns:



187
188
189
190
# File 'lib/wowsql/storage.rb', line 187

def update_bucket(name, **options)
  data = request('PATCH', "/api/v1/storage/projects/#{@project_slug}/buckets/#{name}", nil, options)
  StorageBucket.new(data)
end

#upload(bucket_name, file_data, path: nil, file_name: nil) ⇒ StorageFile

Upload a file to a bucket.

Parameters:

  • bucket_name (String)

    Target bucket

  • file_data (IO)

    File-like object (opened in binary mode)

  • path (String, nil) (defaults to: nil)

    File path within bucket

  • file_name (String, nil) (defaults to: nil)

    Override filename

Returns:



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/wowsql/storage.rb', line 209

def upload(bucket_name, file_data, path: nil, file_name: nil)
  content = file_data.respond_to?(:read) ? file_data.read : file_data
  name = file_name || (file_data.respond_to?(:path) ? File.basename(file_data.path) : nil) || path || 'file'
  name = name.split('/').last if name.include?('/')

  folder = ''
  if path && path.include?('/')
    folder = path.rpartition('/').first
  end

  params = {}
  params['folder'] = folder unless folder.empty?

  upload_io = Faraday::Multipart::FilePart.new(
    StringIO.new(content.is_a?(String) ? content : content.to_s),
    'application/octet-stream',
    name
  )

  response = @conn.post("/api/v1/storage/projects/#{@project_slug}/buckets/#{bucket_name}/files") do |req|
    req.params = params unless params.empty?
    req.body = { file: upload_io }
  end

  if response.status >= 400
    handle_error(response)
  end

  StorageFile.new(response.body)
rescue Faraday::Error => e
  raise StorageError.new("Upload failed: #{e.message}")
end

#upload_from_path(file_path, bucket_name: 'default', path: nil) ⇒ StorageFile

Upload a file from a local filesystem path.

Parameters:

  • file_path (String)

    Path to local file

  • bucket_name (String) (defaults to: 'default')

    Target bucket

  • path (String, nil) (defaults to: nil)

    File path within bucket

Returns:

Raises:



248
249
250
251
252
253
254
255
# File 'lib/wowsql/storage.rb', line 248

def upload_from_path(file_path, bucket_name: 'default', path: nil)
  raise StorageError.new("File not found: #{file_path}") unless File.exist?(file_path)

  file_name = File.basename(file_path)
  File.open(file_path, 'rb') do |f|
    upload(bucket_name, f, path: path || file_name, file_name: file_name)
  end
end