pgchief 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 72ba9fb5a28f3128a49fa7944b30154be2de8b785a24295bae9c3b4544c29c3d
4
+ data.tar.gz: b049ed99e5365e1fd78d727947950aa99e3e5426d9aa76bb4fd77ffdea091d73
5
+ SHA512:
6
+ metadata.gz: b8cfe8f92033c986892e551b8cdc6ff1f7847ed91010ec38ffdd73d2283000b02ae70b9669a79eb4509d5c69c2449fd8f8891d2a5d7a75ccf2372ab141f83087
7
+ data.tar.gz: d8ee3dadb057da4338ab8467040f286c4de2f4383a62a162204de8a686da42734aab4ccfe7e768dc53ef94962b432c3e4be8269129f2ce10bde727bd31129318
data/.env.sample ADDED
@@ -0,0 +1,2 @@
1
+ DATABASE_URL=
2
+ TEST_DATABASE_URL=
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,22 @@
1
+ require:
2
+ - rubocop-performance
3
+ - rubocop-rake
4
+ - rubocop-rspec
5
+
6
+ AllCops:
7
+ TargetRubyVersion: 3.0
8
+ NewCops: enable
9
+
10
+ Style/StringLiterals:
11
+ EnforcedStyle: double_quotes
12
+
13
+ Style/StringLiteralsInInterpolation:
14
+ EnforcedStyle: double_quotes
15
+
16
+ Metrics/BlockLength:
17
+ Exclude:
18
+ - 'spec/**/*'
19
+ - '*.gemspec'
20
+
21
+ RSpec/MultipleExpectations:
22
+ Max: 5
data/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-07-20
4
+
5
+ - Initial release
6
+ - Create database ✅
7
+ - Create user ✅
8
+ - Drop database ✅
9
+ - Drop user ✅
10
+ - List databases ✅
@@ -0,0 +1,132 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, caste, color, religion, or sexual
10
+ identity and orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment for our
18
+ community include:
19
+
20
+ * Demonstrating empathy and kindness toward other people
21
+ * Being respectful of differing opinions, viewpoints, and experiences
22
+ * Giving and gracefully accepting constructive feedback
23
+ * Accepting responsibility and apologizing to those affected by our mistakes,
24
+ and learning from the experience
25
+ * Focusing on what is best not just for us as individuals, but for the overall
26
+ community
27
+
28
+ Examples of unacceptable behavior include:
29
+
30
+ * The use of sexualized language or imagery, and sexual attention or advances of
31
+ any kind
32
+ * Trolling, insulting or derogatory comments, and personal or political attacks
33
+ * Public or private harassment
34
+ * Publishing others' private information, such as a physical or email address,
35
+ without their explicit permission
36
+ * Other conduct which could reasonably be considered inappropriate in a
37
+ professional setting
38
+
39
+ ## Enforcement Responsibilities
40
+
41
+ Community leaders are responsible for clarifying and enforcing our standards of
42
+ acceptable behavior and will take appropriate and fair corrective action in
43
+ response to any behavior that they deem inappropriate, threatening, offensive,
44
+ or harmful.
45
+
46
+ Community leaders have the right and responsibility to remove, edit, or reject
47
+ comments, commits, code, wiki edits, issues, and other contributions that are
48
+ not aligned to this Code of Conduct, and will communicate reasons for moderation
49
+ decisions when appropriate.
50
+
51
+ ## Scope
52
+
53
+ This Code of Conduct applies within all community spaces, and also applies when
54
+ an individual is officially representing the community in public spaces.
55
+ Examples of representing our community include using an official email address,
56
+ posting via an official social media account, or acting as an appointed
57
+ representative at an online or offline event.
58
+
59
+ ## Enforcement
60
+
61
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
62
+ reported to the community leaders responsible for enforcement at
63
+ [INSERT CONTACT METHOD].
64
+ All complaints will be reviewed and investigated promptly and fairly.
65
+
66
+ All community leaders are obligated to respect the privacy and security of the
67
+ reporter of any incident.
68
+
69
+ ## Enforcement Guidelines
70
+
71
+ Community leaders will follow these Community Impact Guidelines in determining
72
+ the consequences for any action they deem in violation of this Code of Conduct:
73
+
74
+ ### 1. Correction
75
+
76
+ **Community Impact**: Use of inappropriate language or other behavior deemed
77
+ unprofessional or unwelcome in the community.
78
+
79
+ **Consequence**: A private, written warning from community leaders, providing
80
+ clarity around the nature of the violation and an explanation of why the
81
+ behavior was inappropriate. A public apology may be requested.
82
+
83
+ ### 2. Warning
84
+
85
+ **Community Impact**: A violation through a single incident or series of
86
+ actions.
87
+
88
+ **Consequence**: A warning with consequences for continued behavior. No
89
+ interaction with the people involved, including unsolicited interaction with
90
+ those enforcing the Code of Conduct, for a specified period of time. This
91
+ includes avoiding interactions in community spaces as well as external channels
92
+ like social media. Violating these terms may lead to a temporary or permanent
93
+ ban.
94
+
95
+ ### 3. Temporary Ban
96
+
97
+ **Community Impact**: A serious violation of community standards, including
98
+ sustained inappropriate behavior.
99
+
100
+ **Consequence**: A temporary ban from any sort of interaction or public
101
+ communication with the community for a specified period of time. No public or
102
+ private interaction with the people involved, including unsolicited interaction
103
+ with those enforcing the Code of Conduct, is allowed during this period.
104
+ Violating these terms may lead to a permanent ban.
105
+
106
+ ### 4. Permanent Ban
107
+
108
+ **Community Impact**: Demonstrating a pattern of violation of community
109
+ standards, including sustained inappropriate behavior, harassment of an
110
+ individual, or aggression toward or disparagement of classes of individuals.
111
+
112
+ **Consequence**: A permanent ban from any sort of public interaction within the
113
+ community.
114
+
115
+ ## Attribution
116
+
117
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118
+ version 2.1, available at
119
+ [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
120
+
121
+ Community Impact Guidelines were inspired by
122
+ [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
123
+
124
+ For answers to common questions about this code of conduct, see the FAQ at
125
+ [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
126
+ [https://www.contributor-covenant.org/translations][translations].
127
+
128
+ [homepage]: https://www.contributor-covenant.org
129
+ [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
130
+ [Mozilla CoC]: https://github.com/mozilla/diversity
131
+ [FAQ]: https://www.contributor-covenant.org/faq
132
+ [translations]: https://www.contributor-covenant.org/translations
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Joel Oliveira
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Joel Oliveira
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,112 @@
1
+ # pgchief
2
+
3
+ I don't need [an entire web application](https://www.pgadmin.org/) to manage
4
+ some self-hosted postgres servers.
5
+
6
+ I also don't have the entirety of every PG command to back up and restore
7
+ databases, manage users, assign permissions to databases, etc, catalogued in my
8
+ brain. (That space is reserved for ... I don't know, obscure comic book characters)
9
+
10
+ Now, what *would* be helpful? That is to have something that assists in some of those
11
+ DB maintenance tasks with a clean and straight-forward CLI interface.
12
+
13
+ Hence, why I am making this `pgchief` thing. It's a simple ruby script utilizing
14
+ the [tty-prompt](https://github.com/piotrmurach/tty-prompt) ruby gem to collect
15
+ necessary info, and do the proper work for me.
16
+
17
+ ***
18
+
19
+ *NOTE*: Very little has been built. This is in active pre-alpha development. See
20
+ below for the feature check-list and current progress.
21
+
22
+ ***
23
+
24
+ ## Usage:
25
+
26
+ ```
27
+ gem install pgchief
28
+
29
+ # make sure the DATABASE_URL is set to the connection string for a pg server's superuser
30
+ export DATABASE_URL=postgresql://postgres:password@postgres.local:5432
31
+
32
+ pgchief
33
+ ```
34
+
35
+ ## Development of the gem
36
+
37
+ 1. Clone this repo.
38
+ 2. `bundle install`
39
+ 3. `cp .env.sample .env`
40
+ 4. Edit `.env` and change:
41
+ * `DATABASE_URL` to point to your main pg instance's superuser account with a connection string.
42
+ * `TEST_DATABASE_URL` to point to your local pg instance where tests can be run against.
43
+ 5. `bundle exec rake` to run test suite & rubocop.
44
+
45
+ ## The ideal, aspirational, DX:
46
+
47
+ ```
48
+ $ pgchief --init # create the TOML file in your home dir (w/600 permissions)
49
+ $ pgchief
50
+
51
+ Welcome! How can I help?
52
+ ‣ Database management
53
+ User management
54
+
55
+ # --- Database management: Creating a DB ---
56
+
57
+ Database management, got it! What's next?
58
+ ‣ Create one
59
+ Drop one
60
+ Back it up
61
+ Restore one
62
+
63
+ What is the database's name?
64
+
65
+ # --- Database management: Dropping a DB ---
66
+
67
+ Which database would you like to drop?
68
+ ‣ fabulous-filly
69
+ great-grape
70
+ faster-fuscia
71
+
72
+ # --- User management: Create a user ---
73
+
74
+ User management? Ok! Who and what?
75
+ ‣ Create user
76
+ Allow access to database
77
+
78
+ What is the user's name?
79
+ rando-username
80
+
81
+ Give "rando-username" access to database(s):
82
+ ‣ ⬡ fabulous-filly
83
+ ⬡ great-grape
84
+ ⬡ faster-fuscia
85
+ ⬡ none of the above
86
+
87
+ # ... etc.
88
+ ```
89
+
90
+ Format of `~/.pgchief.toml`
91
+
92
+ ```toml
93
+ pgurl = "postgres://username:password@host:5432"
94
+ backup_dir = "~/.pg_backups"
95
+
96
+ # [optional] encryption key (to display hashed passwords)
97
+ # encryption_key = "my-password"
98
+ ```
99
+
100
+ ***
101
+
102
+ ## Feature Roadmap
103
+
104
+ - [x] Create database
105
+ - [x] Create user
106
+ - [x] Drop database
107
+ - [x] Drop user
108
+ - [x] List databases
109
+ - [ ] Give user permissions to use database
110
+ - [ ] Back up database
111
+ - [ ] Restore database
112
+ - [ ] Initialize toml file
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ # Set up Rubocop tasks
9
+ require "rubocop/rake_task"
10
+ RuboCop::RakeTask.new(:rubocop) do |t|
11
+ t.options = [
12
+ "--autocorrect-all",
13
+ "--cache=true",
14
+ "--display-cop-names",
15
+ "--display-time",
16
+ "--parallel"
17
+ ]
18
+ end
19
+
20
+ task default: %i[spec rubocop]
data/exe/pgchief ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # frozen_string_literal: true
4
+
5
+ libdir = "#{__dir__}/../lib"
6
+ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
7
+
8
+ require "pgchief"
9
+
10
+ Pgchief::Prompt::Start.call
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pgchief
4
+ module Command
5
+ # Base class for commands
6
+ class Base
7
+ def self.call(*params)
8
+ new(*params).call
9
+ end
10
+
11
+ attr_reader :params, :conn
12
+
13
+ def initialize(*params)
14
+ @params = params
15
+ @conn = PG.connect(ENV.fetch("DATABASE_URL"))
16
+ end
17
+
18
+ def call
19
+ raise NotImplementedError
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pgchief
4
+ module Command
5
+ # Command object to create a database
6
+ class DatabaseCreate < Base
7
+ attr_reader :database
8
+
9
+ def call
10
+ @database = params.first
11
+ raise Pgchief::Errors::DatabaseExistsError if db_exists?
12
+
13
+ conn.exec("CREATE DATABASE #{database}")
14
+
15
+ "Database '#{database}' created successfully!"
16
+ rescue PG::Error => e
17
+ "Error: #{e.message}"
18
+ ensure
19
+ conn.close
20
+ end
21
+
22
+ private
23
+
24
+ def db_exists?
25
+ query = "SELECT 1 FROM pg_database WHERE datname = '#{database}'"
26
+ conn.exec(query).any?
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pgchief
4
+ module Command
5
+ # Command object to drop a database
6
+ class DatabaseDrop < Base
7
+ attr_reader :database
8
+
9
+ def call
10
+ @database = params.first
11
+ raise Pgchief::Errors::DatabaseMissingError unless db_exists?
12
+
13
+ conn.exec("DROP DATABASE #{database}")
14
+
15
+ "Database '#{database}' dropped successfully!"
16
+ rescue PG::Error => e
17
+ "Error: #{e.message}"
18
+ ensure
19
+ conn.close
20
+ end
21
+
22
+ private
23
+
24
+ def db_exists?
25
+ query = "SELECT 1 FROM pg_database WHERE datname = '#{database}'"
26
+ conn.exec(query).any?
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pgchief
4
+ module Command
5
+ # Class to list databases
6
+ class DatabaseList < Base
7
+ def call
8
+ prompt = TTY::Prompt.new
9
+ databases = Pgchief::Database.all.join("\n")
10
+
11
+ prompt.say databases
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pgchief
4
+ module Command
5
+ # Class to create a user
6
+ class UserCreate < Base
7
+ USER_OPTIONS = %w[
8
+ NOINHERIT
9
+ NOCREATEDB
10
+ NOCREATEROLE
11
+ NOSUPERUSER
12
+ NOREPLICATION
13
+ ].freeze
14
+
15
+ attr_reader :username, :password
16
+
17
+ def call
18
+ @username, @password = params
19
+ raise Pgchief::Errors::UserExistsError if user_exists?
20
+
21
+ conn.exec("CREATE USER #{username} WITH #{user_options} PASSWORD '#{password}'")
22
+
23
+ "User '#{username}' created successfully!"
24
+ rescue PG::Error => e
25
+ "Error: #{e.message}"
26
+ ensure
27
+ conn.close
28
+ end
29
+
30
+ private
31
+
32
+ def user_exists?
33
+ query = "SELECT 1 FROM pg_user WHERE usename = '#{username}'"
34
+ conn.exec(query).any?
35
+ end
36
+
37
+ def user_options
38
+ USER_OPTIONS.join(" ")
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pgchief
4
+ module Command
5
+ # Class to drop a user
6
+ class UserDrop < Base
7
+ attr_reader :username
8
+
9
+ def call
10
+ @username = params.first
11
+ raise Pgchief::Errors::UserExistsError unless user_exists?
12
+
13
+ conn.exec("DROP USER #{username}")
14
+
15
+ "User '#{username}' dropped successfully!"
16
+ rescue PG::Error => e
17
+ "Error: #{e.message}"
18
+ ensure
19
+ conn.close
20
+ end
21
+
22
+ def user_exists?
23
+ query = "SELECT 1 FROM pg_user WHERE usename = '#{username}'"
24
+ conn.exec(query).any?
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pgchief
4
+ module Command
5
+ # Class to list databases
6
+ class UserList < Base
7
+ def call
8
+ prompt = TTY::Prompt.new
9
+ users = Pgchief::User.all.join("\n")
10
+
11
+ prompt.say users
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pgchief
4
+ # Base module for command classes
5
+ module Command; end
6
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pg"
4
+
5
+ module Pgchief
6
+ # Database information and operations
7
+ class Database
8
+ def self.all
9
+ conn = PG.connect(ENV.fetch("DATABASE_URL"))
10
+ result = conn.exec("SELECT datname FROM pg_database WHERE datistemplate = false")
11
+ result
12
+ .map { |row| row["datname"] }
13
+ .reject { |name| name == "postgres" }
14
+ ensure
15
+ conn.close
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pgchief
4
+ module Prompt
5
+ # Base class for prompt classes
6
+ class Base
7
+ def self.class
8
+ raise "Method not defined"
9
+ end
10
+
11
+ def self.klassify(scope, words)
12
+ Object.const_get([
13
+ "Pgchief", "::", scope.capitalize, "::",
14
+ words.split.map(&:capitalize)
15
+ ].flatten.join)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pgchief
4
+ module Prompt
5
+ # Class to ask for database name, in order to create it
6
+ class CreateDatabase < Base
7
+ def self.call
8
+ prompt = TTY::Prompt.new
9
+ database = prompt.ask("Database name:")
10
+ result = Pgchief::Command::DatabaseCreate.call(database)
11
+
12
+ prompt.say result
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pgchief
4
+ module Prompt
5
+ # Class to prompt for user creation details
6
+ class CreateUser < Base
7
+ def self.call
8
+ prompt = TTY::Prompt.new
9
+ username = prompt.ask("Username:")
10
+ password = prompt.mask("Password:")
11
+ result = Pgchief::Command::UserCreate.call(username, password)
12
+
13
+ prompt.say result
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pgchief
4
+ module Prompt
5
+ # Class to manage database operations
6
+ class DatabaseManagement < Base
7
+ def self.call
8
+ prompt = TTY::Prompt.new
9
+ result = prompt.select("Database management", ["Create database", "Drop database", "Database List"])
10
+ scope = result == "Database List" ? "command" : "prompt"
11
+
12
+ klassify(scope, result).call
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pgchief
4
+ module Prompt
5
+ # Class to prompt for which database to drop
6
+ class DropDatabase < Base
7
+ def self.call
8
+ prompt = TTY::Prompt.new
9
+ database = prompt.select("Which database needs to be dropped?", Pgchief::Database.all)
10
+ result = Pgchief::Command::DatabaseDrop.call(database)
11
+
12
+ prompt.say result
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pgchief
4
+ module Prompt
5
+ # Class to prompt for which user to drop
6
+ class DropUser < Base
7
+ def self.call
8
+ prompt = TTY::Prompt.new
9
+ user = prompt.select("Which user needs to be deleted?", Pgchief::User.all)
10
+ result = Pgchief::Command::UserDrop.call(user)
11
+
12
+ prompt.say result
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pgchief
4
+ module Prompt
5
+ # Class to ask for database names, in order to create it
6
+ class GrantDatabasePrivileges
7
+ def self.call(username)
8
+ databases = Pgchief::Database.all
9
+ prompt = TTY::Prompt.new
10
+ databases = prompt.select("Select database:", databases, multiselect: true)
11
+ Pgchief::Command::GrantDatabasePrivileges.call(username, databases)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pgchief
4
+ module Prompt
5
+ # Kicks off the CLI with an initial prompt
6
+ class Start < Base
7
+ def self.call
8
+ prompt = TTY::Prompt.new
9
+ result = prompt.select(
10
+ "Welcome! How can I help?",
11
+ [
12
+ "Database management",
13
+ "User management"
14
+ ]
15
+ )
16
+
17
+ klassify("prompt", result).call
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pgchief
4
+ module Prompt
5
+ # Class to manage users
6
+ class UserManagement < Base
7
+ def self.call
8
+ prompt = TTY::Prompt.new
9
+ result = prompt.select("User management", ["Create user", "Drop user", "User list"])
10
+
11
+ scope = result == "User list" ? "command" : "prompt"
12
+ klassify(scope, result).call
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pgchief
4
+ # Base module for prompt classes
5
+ module Prompt; end
6
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pg"
4
+
5
+ module Pgchief
6
+ # Database information and operations
7
+ class User
8
+ def self.all
9
+ conn = PG.connect(ENV.fetch("DATABASE_URL"))
10
+ result = conn.exec("SELECT usename FROM pg_user")
11
+
12
+ result.map { |row| row["usename"] }.reject { |name| name == "postgres" }
13
+ ensure
14
+ conn.close
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pgchief
4
+ VERSION = "0.1.0"
5
+ end
data/lib/pgchief.rb ADDED
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pg"
4
+ require "tty-prompt"
5
+ require "pgchief/version"
6
+
7
+ require "pgchief/database"
8
+ require "pgchief/user"
9
+ require "pgchief/prompt/base"
10
+ require "pgchief/prompt/start"
11
+ require "pgchief/prompt/create_database"
12
+ require "pgchief/prompt/create_user"
13
+ require "pgchief/prompt/database_management"
14
+ require "pgchief/prompt/drop_database"
15
+ require "pgchief/prompt/drop_user"
16
+ require "pgchief/prompt/user_management"
17
+
18
+ require "pgchief/command/base"
19
+ require "pgchief/command/database_create"
20
+ require "pgchief/command/database_drop"
21
+ require "pgchief/command/database_list"
22
+ require "pgchief/command/user_create"
23
+ require "pgchief/command/user_drop"
24
+ require "pgchief/command/user_list"
25
+
26
+ module Pgchief
27
+ class Error < StandardError; end
28
+
29
+ module Errors
30
+ class UserExistsError < Error; end
31
+ class DatabaseExistsError < Error; end
32
+ class DatabaseMissingError < Error; end
33
+ end
34
+ end
data/sig/pgchief.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Pgchief
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,137 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pgchief
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Joel Oliveira
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-08-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: pg
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: toml-rb
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: tty-option
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: tty-prompt
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description:
70
+ email:
71
+ - joel.oliveira@gmail.com
72
+ executables:
73
+ - pgchief
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".env.sample"
78
+ - ".rspec"
79
+ - ".rubocop.yml"
80
+ - CHANGELOG.md
81
+ - CODE_OF_CONDUCT.md
82
+ - LICENSE
83
+ - LICENSE.txt
84
+ - README.md
85
+ - Rakefile
86
+ - exe/pgchief
87
+ - lib/pgchief.rb
88
+ - lib/pgchief/command.rb
89
+ - lib/pgchief/command/base.rb
90
+ - lib/pgchief/command/database_create.rb
91
+ - lib/pgchief/command/database_drop.rb
92
+ - lib/pgchief/command/database_list.rb
93
+ - lib/pgchief/command/user_create.rb
94
+ - lib/pgchief/command/user_drop.rb
95
+ - lib/pgchief/command/user_list.rb
96
+ - lib/pgchief/database.rb
97
+ - lib/pgchief/prompt.rb
98
+ - lib/pgchief/prompt/base.rb
99
+ - lib/pgchief/prompt/create_database.rb
100
+ - lib/pgchief/prompt/create_user.rb
101
+ - lib/pgchief/prompt/database_management.rb
102
+ - lib/pgchief/prompt/drop_database.rb
103
+ - lib/pgchief/prompt/drop_user.rb
104
+ - lib/pgchief/prompt/grant_database_priveleges.rb
105
+ - lib/pgchief/prompt/start.rb
106
+ - lib/pgchief/prompt/user_management.rb
107
+ - lib/pgchief/user.rb
108
+ - lib/pgchief/version.rb
109
+ - sig/pgchief.rbs
110
+ homepage: https://github.com/jayroh/pgchief
111
+ licenses:
112
+ - MIT
113
+ metadata:
114
+ homepage_uri: https://github.com/jayroh/pgchief
115
+ source_code_uri: https://github.com/jayroh/pgchief
116
+ changelog_uri: https://github.com/jayroh/pgchief/blob/main/CHANGELOG.md
117
+ rubygems_mfa_required: 'true'
118
+ post_install_message:
119
+ rdoc_options: []
120
+ require_paths:
121
+ - lib
122
+ required_ruby_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: 3.0.0
127
+ required_rubygems_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ requirements: []
133
+ rubygems_version: 3.5.11
134
+ signing_key:
135
+ specification_version: 4
136
+ summary: A simple ruby script to manage postgresql databases and users
137
+ test_files: []