MySaaS is an open-source, extensible and scalable platform for develop your own Software as a Service (SaaS), e-Commerce, Education Platform, Social Network, Forum or any kind of memberships based product.
- On Ubuntu 20.04, download the script for installing our standard environment:
wget https://raw.githubusercontent.com/leandrosardi/environment/main/sh/install.ubuntu.20_04.sh
- Install my.saas:
Switch to root
and run this command:
bash install.ubuntu.20_04.sh <hostname> <password>
-
A new user
blackstack
will be added to your operative system, with the password provided in the command line. -
The
hostname
of your computer will be changed too.
-
Switch user to
blackstack
. -
Install and run your first SaaS:
saas deploy ssh=blackstack@<password>@localhost:22
The following (and very simple architecture) has been installed in your computer:
(image)
- Visit your SaaS at http://127.0.0.1:3000.
Recommended readings:
Outline:
Getting Started
Models
- Persistence Classes
- Creating Objects x. Core Model (review of the persistance classes) x. Helper Functions (review most frequently used blackstack core functions)
Screens
Back-End Processing
- Stand Alone Processes
- Distributed Processes
- Adding Javascript Libraries
- Adding Stylesheet Libraries
- Adding Images
Configurations
- Setting Logo
- Setting Left Bar
- Setting Footer (config.rb, and extension is deprecated)
- Adding Extensions (config.rb, customizing extension settings, and extension dependencies)
- Funnel (ga, captchas, screens workflow)
- Super-User
- Elastic Storage
- Sandbox Mode
- Debugging
- Transactional Emails
Deploying
- Versioning (config.rb, and extension)
- Secret Files
- Deployment
Configuration
- changing deloyment output file
- changing
MYSAAS_API_KEY
- setting
DROPBOX_REFRESH_TOKEN
Here is a full list of the MySaaS features:
Fast Front-End Coding:
- ✔️ Vast pool of JavaScript components for an imporved UX.
- ✔️ Vast pool of CSS entities for a nice UI.
- ✔️ Funnel configuration with JSON descriptors.
Deployment Automation:
- ✔️ Automated installation CLI command.
- ✔️ Automated deployment CLI command.
- ✔️ Automated backing up and restoring of secret files that you can't store in repositories (DropBox).
Scalability:
- ✔️ Infrasctructure as a Code (IaaC) capabilities for running a mesh of offline processes.
- ✔️ Scalable architecture:
- AWS EC2 for scaleble webservers;
- Dropbox API for elastic storage; and
- CockroachDB for scalable and serverless database.
End-Users Management:
- ✔️ Transactional emails (Postmark).
- ✔️ PayPal integration for Invoicing & Payments Processing (I2P).
Extensibility:
- ✔️ Easy modules development.
- ✔️ Advanced JavaScript components:
On a fresh installation of Ubuntu 20.04, and using the root
user, run the commands below:
Step 1: Run the Environment Installation Script.
Download our Environment Installation Script (or simply EIS) by running the lines below:
cd ~
wget https://raw.githubusercontent.com/leandrosardi/environment/main/sh/install.ubuntu.20_04.sh
Then run the EIS by running the line below, replacing the merge-tags
<hostname>
with the name you have to assisng the the node where my.saas will run; and<password>
with the password you want for a new Linux user calledblackstack
who will be created by the EIS.
bash install.ubuntu.20_04.sh <hostname> <password>
Note: For running this example, use the values dev2
and blackstack123
. E.g.:
bash install.ubuntu.20_04.sh dev2 blackstack123
Note: Refer to this other repository for more information about the Environment Installation Script.
Step 2: Switch from root
to the new blackstack
user.
sudo su - blackstack
Step 3: Clone my.saas.
mkdir ~/code
cd ~/code
git clone https://github.com/leandrosardi/my.saas
cd ~/code/my.saas
git switch dev-1.6.7
git fetch --all
git reset --hard origin/dev-1.6.7
Step 4: Update gems.
cd ~/code/my.saas
bundler update
Step 5: Create a configuration file.
cd ~/code/my.saas
mv config.template.rb config.rb
Step 6: Deploy my.saas.
export RUBYLIB=~/code/my.saas
cd ~/code/my.saas/cli
ruby deploy.rb
Now, you can navigatoe the my.saas website from the host browser:
You can login using the default credentials:
- user:
su
- password:
Testing123
If the website is not working, you can check the file ~/deployment.log
in the server node.
Every time you want to run a Ruby script using the my.saas framework, you have to set the environment variable RUBYLIB
properly.
export RUBYLIB=/home/blackstcack/code/my.saas
E.g.: This signup example registers a new account in the database.
export RUBYLIB=/home/blackstcack/code/my.saas
cd $RUBYLIB/examples
ruby signup.rb
Every new script must looks like below:
# load gems and connect database
require 'mysaas'
require 'lib/stubs'
require 'config'
require 'version'
DB = BlackStack.db_connect
require 'lib/skeletons'
# create a new l
l = BlackStack::LocalLogger.new('<your-script-name-here>.log')
begin
l.logs 'Starting the process... '
# TODO: Add your code here.
l.logf 'done'.green
# catch the case when the process is interrupted
rescue Interrupt => e
l.logf e.message.red
l.logf e.backtrace.join("\n")
# catch the case when an exception is raised
rescue Exception => e
l.logf e.message.red
l.logf e.backtrace.join("\n")
# ensure releasing of resources and the execution of any other mandatory code
ensure
GC.start
DB.disconnect
# TODO: add any other mandatory code here.
l.log 'process finished'.green
end
If you want to add screens to My.SaaS, you shouldn't for the my.saas repository, because each time @leandrosardi releases a new version of my.saas you should have to merge such a new version with your fork.
To to disengage the my.saas core from your project, my.saas provide a feature called extensions.
Follow these steps to create your own extension:
Step 1: Fork the my.saas extensions template, as a new repository in your GitHub organization.
E.g.: Your extension may be called my.extension.
Step 2: Pull your new repository in your local computer.
E.g.: If the name of your organization is acme and the name of your extension is my.extension, you should execute the following commands:
cd $HOME/code/my.saas/extensions
git clone https://github.com/acme/my.extension
and you will find the source code of your extension in the folder $HOME/code/my.saas/extensions/my.extension
.
Step 3: Edit the merge-tags in the source code of your extension.
E.g.: If the name of your organization is acme and the name of your extension is my.extension, you should replace the following merge-tags in the source code into the folder $HOME/code/my.saas/extensions/my.extension
:
merge-tag | replacement value |
---|---|
<!your-github-username!> | acme |
<!your-github-repo-name-for-this-extension!> | my.extension |
<!extension-description-here!> | Example exension to become a My.SaaS expert. |
Step 4: Rename the folder <!your-github-repo-name-for-this-extension!>
.
E.g.: If the name of your extension is my.extension, you should run the following command:
mv $HOME/code/my.saas/extensions/my.extension/public/<!your-github-repo-name-for-this-extension!> $HOME/code/my.saas/extensions/my.extension/public/my.extension
The folder sql contains the database schema and the seed data for running my.saas. Read the files into the folder sql and get familiarized with them.
The folder models/skeleton has Sequel defined clases to manage objects that persist in the database. These classes are called persistence classes.
Here are the guidelines for writing persistence classes:
Guideline 1. Each persistence class must be placed in one different .rb
file.
E.g.: You create the file models/skeleton/account.rb to place the code of the persistence class for the table account.
Guideline 2. Each file added to the folder models/skeleton, must be required from the file [lib/skeletons.rb]. Such a require
must looks like follows:
require "/extensions/<name of the project module>/models/skeleton/<name of the file with a persistence class>"
E.g.:
require "/extensions/<name of the project module>/models/skeleton/account.rb"
Note: If you don't know the name of the project module, ask it to @leandrosardi.
Guideline 3. Any persistence class should inherit from Sequel::Model
.
E.g.: In the file models/skeleton/account.rb you write a Ruby code like this:
class Account < Sequel::Model(:account)
# class attributes and method are here
end # class Account
Guideline 4. Any persistence class must be contained into the project module that you are working on (e.g.: my.extension
).
If you don't know the name of the project module, ask it to @leandrosardi.
E.g.: In the file models/skeleton/account.rb you write a Ruby code like this:
module <name of the project module>
class Account < Sequel::Model(:account)
# class attributes and method are here
end # class Account
end # module <name of the project module>
Guideline 5. The project module must be contained into the BlackStack
module too.
E.g.: In the file models/skeleton/account.rb you write a Ruby code like this:
module BlackStack
module <name of the project module>
class Account < Sequel::Model(:account)
# class attributes and method are here
end # class Account
end # module <name of the project module>
end # module BlackStack
Guideline 6. Any persistence class must have an array attribute for each other persistence class who has a foreing key to it.
E.g.: In the file models/skeleton/account.rb you write a Ruby code like this:
module BlackStack
module <name of the project module>
class Account < Sequel::Model(:account)
# array attribute for each other **persistence class** who has a foreing key to it.
one_to_many :users, :class=>:'BlackStack::MySaaS::User', :key=>:id_account
end # class Account
end # module <name of the project module>
end # module BlackStack
Guideline 7. Any persistence class must have an attribute for each other persistence class that it has a foreing key to.
E.g.: In the file models/skeleton/account.rb you write a Ruby code like this:
module BlackStack
module <name of the project module>
class Account < Sequel::Model(:account)
# array attribute for each other **persistence class** who has a foreing key to it.
one_to_many :users, :class=>:'BlackStack::MySaaS::User', :key=>:id_account
# attribute for each other **persistence class** that it has a foreing key to.
many_to_one :timezone, :class=>:'BlackStack::MySaaS::Timezone', :key=>:id_timezone
end # class Account
end # module <name of the project module>
end # module BlackStack
Guideline 8. Some AI assistants use to add blackstack_db
as a plugin of the sequel classes. That is not correct.
E.g.: The class below has a line plugin :blackstack_db
. That is not necessary.
module BlackStack
module <name of the project module>
class Account < Sequel::Model(:account)
plugin :blackstack_db
# array attribute for each other **persistence class** who has a foreing key to it.
one_to_many :users, :class=>:'BlackStack::MySaaS::User', :key=>:id_account
# attribute for each other **persistence class** that it has a foreing key to.
many_to_one :timezone, :class=>:'BlackStack::MySaaS::Timezone', :key=>:id_timezone
end # class Account
end # module <name of the project module>
end # module BlackStack
Use the function guid
if you need a Globally Unique ID (or simply GUID) for creating a new persistence object.
Use the function now
if you need the current timestamp.
The function now
returns the current timestamp at GTM-3 (Buenos Aires date/time).
E.g.: The script below will register a new account in the database.
# load gem and connect database
require 'mysaas'
require 'lib/stubs'
require 'config'
require 'version'
DB = BlackStack.db_connect
require 'lib/skeletons'
a = BlackStack::MySaaS::Account.new
a.id = guid
a.id_account_owner = BlackStack::MySaaS::Account.first.id
a.name = 'ACME LLC'
a.create_time = now
a.id_timezone = BlackStack::MySaaS::Timezone.first.id
a.save
If you want to add a new screen, you have to modify the app.rb file.
E.g.: Follow the steps below to add a new "demo page" to your project.
- add the lines below into the app.rb file:
get '/demo' do
"Deno Screen"
end
- Restart the webserver by running the start.rb script.
cd $RUBYLIB/cli
ruby start.rb
You can embeed your HTML code into the same frame used by the login screen seen before.
- Save your HTML code into a new file into the views. In this example, it would be views/demo.erb.
Demo Screen
- Write again the entry in the app.rb file.
get '/demo' do
erb :'views/demo', :layout => :'/views/layouts/public'
end
Remember to restart the webserver by running the start.rb script.
cd $RUBYLIB/cli
ruby start.rb
The private pages are pages that require a login.
E.g.: Follow the steps below to a survey screen to show up every when a new user signup.
- Save your HTML code into a new file into the views. In this example, it would be views/survey.erb.
<h1>Survey Screen</h1>
- Write again the entry in the app.rb file.
get '/survey', :auth => true do
erb :'views/survey', :layout => :'/views/layouts/core'
end
The auth
condition will get the page forwarding to login if there are not any user logged in.
The core
layout will embbed your HTML into a frame designed for private pages only.
Remember to restart the webserver by running the start.rb script.
cd $RUBYLIB/cli
ruby start.rb
Add the code below at the top if the /views/survey.erb screen, to add a 2-level navigation bar to your page.
<div class="mynavbar2 mysticky">
<div class="row-fluid">
<div class="span3">
<%=nav1("Survey")%>
</div>
</div>
</div>
In your survey page, a 2-level navigation bar will look like this:
ACME LLC > Survey
where ACME LLC
is the the value in the field name
of the record in the account
table, to which the logged in user is beloning to.
Inside the .mynavbar2
div, you can add a second child div to place buttons.
E.g.: The code below adds one blue button to submit the survey, and one gray button to skip the survey.
<div class="mynavbar2 mysticky">
<div class="row-fluid">
<div class="span3">
<%=nav1("Survey")%>
</div>
</div>
<div class="span9">
<button class='btn btn-blue'>Submit</button>
<a class='btn btn-gray' href='./'>Skip</a>
</div>
</div>
Note: The second button is really a link to the dashboard.
Buttons may have nice tooltips too.
E.g.: The code below includes a tooltip to the submit button:
<div class="mynavbar2 mysticky">
<div class="row-fluid">
<div class="span3">
<%=nav1("Survey")%>
</div>
</div>
<div class="span9">
<button class='btn btn-blue' title="Click here after you have filled all the fields of the survey.">Submit</button>
<a class='btn btn-gray' href='./'>Skip</a>
</div>
</div>
You can add inputs too.
E.g.: The code below includes an input box and a button to perform searches.
<div class="mynavbar2 mysticky">
<div class="row-fluid">
<div class="span3">
<%=nav1("Survey")%>
</div>
</div>
<div class="span9">
<button class='btn btn-blue' title="Click here after you have filled all the fields of the survey.">Submit</button>
<a class='btn btn-gray' href='./'>Skip</a>
<input class="input input-large" id="q" name="q" value="">
<button class="btn btn-blue" id="search"><i class="icon-search"></i></button>
</div>
</div>
In the same div where you placed buttons you can also add dropdown menus.
E.g.: The code below adds a list of tasks to perform by the user, in order to help the user to operate the software.
<div class="mynavbar2 mysticky">
<div class="row-fluid">
<div class="span3">
<%=nav1("Survey")%>
</div>
<div class="span9">
<button class='btn btn-blue'>Submit</button>
<a class='btn btn-gray' href='./'>Skip</a>
<div class="btn-group" style="vertical-align:top;">
<a id="tasks_list" name="tasks_list" class="btn dropdown-toggle" data-toggle="dropdown" href="#">
<b>Pending Tasks:</b> Complete the Survey <i class="icon-circle" style="color:gray;"></i> <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li>
<button class="btn btn-link" disabled=""><i class='icon-ok'></i> Signup</button>
</li>
<li>
<button class="btn btn-link" href='/survey'><i class='icon-time'></i> Complete the Survey</i></button>
</li>
<li>
<button class="btn btn-link" href='/settings/account'><i class='icon-time'></i> Setup Your TimeZone</i></button>
</li>
</ul>
</div>
</div>
</div>
</div>
Note: The first task in the list (signup) is already performed. The other 2 tasks are pending and include the href
to there page where such a task should be performed.
Below the .mynavbar2
div you would want to place forms, tables, charts, etc.
You may want to split the screen horizontally.
E.g.: You may want to place a table in the left panel, and show other stats in the right panel.
E.g.: In the survey screen, you a left panel to show a list of users who submited the survey, and a right panel to place the survey form.
<!-- NavBar -->
<div class="mynavbar2 mysticky">
<div class="row-fluid">
<div class="span3">
<%=nav1("Survey")%>
</div>
<div class="span9">
<button class='btn btn-blue' title="Click here after you have filled all the fields of the survey.">Submit</button>
<a class='btn btn-gray' href='./'>Skip</a>
</div>
</div>
</div>
<section class="row-fluid box">
<h1>Survey Screen</h1>
<p>Complete this survey to help us to setup your account in a way that fir your needs perfectly!</p>
<div class='span12'>
<div class='span6'>
<h3>Who Completed the Survey?</h3>
<!-- TODO: Place the list of users beloinig this account here -->
</div>
<div class='span6'>
<h3>Survey</h3>
<!-- TODO: Place the survey form here -->
</div>
</div>
</section>
(pending)
(pending)
(pending)
(pending)
(pending)
(pending)
(pending)
(pending)
(pending)
(pending)
(pending)
(pending)