Compare commits

...

56 Commits

Author SHA1 Message Date
bucky 1822004460 use the hostname of the artifacts repository
Gitea Bucky/url-shortener/pipeline/head There was a failure building this commit
2023-12-19 07:58:59 -08:00
bucky b9345f9cb5 removed extra line
Gitea Bucky/url-shortener/pipeline/head This commit looks good
2023-12-18 20:19:55 -08:00
bucky 166c68570d v1.0.1
Gitea Bucky/url-shortener/pipeline/head There was a failure building this commit
2023-12-18 20:16:52 -08:00
bucky 28fc8b3b86 updated Gemfile with falcon instead of puma
Gitea Bucky/url-shortener/pipeline/head There was a failure building this commit
2023-12-18 20:15:33 -08:00
bucky 2c7a18c374 create the directory before syncingit
Gitea Bucky/url-shortener/pipeline/head This commit looks good
2023-12-18 18:38:18 -08:00
bucky 0a3f6c5fab try with ssh-agent
Gitea Bucky/url-shortener/pipeline/head There was a failure building this commit
2023-12-18 18:11:28 -08:00
bucky ca29051dc2 try to rsync with the user/key pair
Gitea Bucky/url-shortener/pipeline/head There was a failure building this commit
2023-12-18 17:50:42 -08:00
bucky 0b51b52b84 echo the file to see what's in there/how to handle it
Gitea Bucky/url-shortener/pipeline/head This commit looks good
2023-12-18 17:45:55 -08:00
bucky 4659942800 use user/key pair
Gitea Bucky/url-shortener/pipeline/head This commit looks good
2023-12-18 17:33:09 -08:00
bucky 6d0f594bba set file in the stage it is used in
Gitea Bucky/url-shortener/pipeline/head There was a failure building this commit
2023-12-18 17:15:30 -08:00
bucky 4ca73cc3ca use artifacts key
Gitea Bucky/url-shortener/pipeline/head There was a failure building this commit
2023-12-18 16:59:52 -08:00
bucky 1f96facbf2 use the ZIP FILE variable not ZIP_FILE text
Gitea Bucky/url-shortener/pipeline/head There was a failure building this commit
2023-12-18 15:57:02 -08:00
bucky e362cef9ba added SHA256 and sync both the sig and the zip
Gitea Bucky/url-shortener/pipeline/head There was a failure building this commit
2023-12-18 15:51:41 -08:00
bucky 25d94bbfc2 attempt to use rsync to sync data to artifact repo jail
Gitea Bucky/url-shortener/pipeline/head There was a failure building this commit
2023-12-18 13:09:52 -08:00
bucky 40e3703650 get the actual version, not the line
Gitea Bucky/url-shortener/pipeline/head This commit looks good
2023-10-24 18:11:12 -07:00
bucky 21eeb9db72 use version instead of date
Gitea Bucky/url-shortener/pipeline/head There was a failure building this commit
2023-10-24 18:01:15 -07:00
bucky 2cbe5f85b0 moved bundle-audit inside test group, added version file 2023-10-24 17:53:56 -07:00
bucky b62d45d0d8 Merge pull request 'prod-artifact-when-good' (#34) from prod-artifact-when-good into master
Gitea Bucky/url-shortener/pipeline/head This commit looks good
Reviewed-on: #34
2023-10-23 15:54:27 -07:00
bucky 0dc792b0bf added try catch wrappers around other parts. if they fail, don't build
Gitea Bucky/url-shortener/pipeline/head This commit looks good
the deliverable
2023-10-23 15:48:42 -07:00
bucky 5f4209dbe8 testing skipping building the artifact at the end (and cleanup of it)
Gitea Bucky/url-shortener/pipeline/head This commit looks good
2023-10-23 15:10:16 -07:00
bucky fbba67a9bd install, not clean, since it changed the bundle location
Gitea Bucky/url-shortener/pipeline/head This commit looks good
2023-10-23 15:05:20 -07:00
bucky 5cd296cb70 when is outside the steps
Gitea Bucky/url-shortener/pipeline/head There was a failure building this commit
2023-10-23 15:03:45 -07:00
bucky 7c0f6eb603 limit building deployment artifact
Gitea Bucky/url-shortener/pipeline/head There was a failure building this commit
+ added buildArtifact variable and set it to true
+ in auditing stage, if there's an error, set buildArtifact to false
+ in build artifact stage, if buildArtifact is true, build  it
2023-10-23 14:58:02 -07:00
bucky de3dcc48ab removed the when, and removed unnecessary rm command
Gitea Bucky/url-shortener/pipeline/head There was a failure building this commit
2023-10-23 12:31:29 -07:00
bucky 958091e000 see if we can access the buildResult status to use it 2023-10-23 11:32:02 -07:00
bucky 35583e404e move the local path in the build prod artifact stage to make the tests
quicker
2023-10-23 07:43:30 -07:00
bucky 0e9342fab6 only build the production environment when the previous steps all executed successfully 2023-10-23 07:41:52 -07:00
bucky ca4182d798 Merge pull request 'added bundle config to only use prod gems and bundle clean to remove unnecessary gems from the current poject' (#33) from build-deployment-artifact into master
Reviewed-on: #33

closes bucky/url-shortener#32
2023-10-23 07:31:15 -07:00
bucky 7d04c46728 added bundle config to only use prod gems and bundle clean to remove unnecessary gems from the current poject 2023-10-23 07:18:31 -07:00
bucky 4490ed3e7c output something from the bundler audit to see in the reports 2023-10-22 16:22:18 -07:00
bucky 30c1192e27 Merge pull request 'audit' (#31) from audit into master
Reviewed-on: #31

closes bucky/url-shortener#30
2023-10-22 16:06:15 -07:00
bucky 6e30705810 rubocop autofixed quotes 2023-10-22 15:57:57 -07:00
bucky d7c9105995 added audit checks, this should help keep things secure 2023-10-22 15:51:16 -07:00
bucky a732eb2a61 added bundle-audit and ruby_audit gems 2023-10-22 15:48:40 -07:00
bucky c29d53a33b Merge pull request 'rubocop' (#29) from rubocop into master
Reviewed-on: #29

closes bucky/url-shortener#28
2023-10-22 15:11:23 -07:00
bucky 3fd8e8c630 wrap the string in single quotes 2023-10-22 15:00:59 -07:00
bucky 950c358ae5 another try 2023-10-22 14:55:06 -07:00
bucky f81199e31b maybe this will clean up a few things 2023-10-22 14:15:27 -07:00
bucky bf1fefd1bc still worknig on env file, and adding logic to not build if the tests fail 2023-10-22 14:05:51 -07:00
bucky c51a6782d9 another shot at cleaning up the .env.rb file 2023-10-22 13:50:58 -07:00
bucky 795447e029 attempting to clean up the .env.rb file 2023-10-22 13:43:12 -07:00
bucky 7caf25c4d0 don't kill it if linting breaks, mark it as unstable 2023-10-22 13:32:39 -07:00
bucky 3fa1de5e4e install bundled gems in /vendor, and use bundle exec to run them 2023-10-22 13:27:07 -07:00
bucky f86da2ed6f forgot to wrap the steps in a steps block 2023-10-22 13:19:30 -07:00
bucky 282a07c5fb added rubocop linting section with output for reports and clean up
rubocop config
2023-10-22 13:16:20 -07:00
bucky bf6ed14b5e custom parts for rubocop to run. don't worry about block length in many
parts. hide extension suggestions
2023-10-22 13:15:09 -07:00
bucky 7d35133e43 require capybara and rspec parts needed for tests to run instead of
including them. rubocop autofixed formatting
2023-10-22 13:14:23 -07:00
bucky c505012b00 rubocop added frozen string literal comment 2023-10-22 13:14:02 -07:00
bucky 4f54124519 rubocop autofixed formatting 2023-10-22 13:13:27 -07:00
bucky 18e8eac2ab rubocop autofixed formatting 2023-10-22 13:12:48 -07:00
bucky 5abf07d0c7 rubocop formatting fixes 2023-10-22 13:07:07 -07:00
bucky 48ffdf560b frozen string literal for rubocop 2023-10-22 13:06:12 -07:00
bucky e0392f2483 added rubocop 2023-10-22 13:05:43 -07:00
bucky 15a5017c86 set up to run with only the freebsd and ruby labeled agent(s) 2023-10-18 13:08:41 -07:00
bucky dd5caa4056 updated README, swapped Falcon for Puma for now, and ignored more files 2023-10-17 21:53:12 -07:00
bucky 360ddbe6d2 include the branch name in the zip, so it can be identified a little easier later 2023-10-17 21:45:14 -07:00
13 changed files with 305 additions and 101 deletions
+2
View File
@@ -3,3 +3,5 @@
*.db *.db
.env.rb .env.rb
coverage coverage
vendor
.ruby-version
+6
View File
@@ -0,0 +1,6 @@
AllCops:
NewCops: enable
SuggestExtensions: false
Metrics/BlockLength:
AllowedMethods: ['describe', 'context', 'route', 'r.on', 'r.post']
+18 -15
View File
@@ -1,32 +1,35 @@
# frozen_string_literal: true # frozen_string_literal: true
source "https://rubygems.org" source 'https://rubygems.org'
gem 'rackup', '~> 2.1'
gem "roda", "~> 3.72" gem 'falcon', '~> 0.42.3'
gem "sequel", "~> 5.72" gem 'roda', '~> 3.72'
gem "tilt", "~> 2.2" gem 'sequel', '~> 5.72'
gem "sqlite3", "~> 1.6" gem 'tilt', '~> 2.2'
gem "erubi", "~> 1.12" gem 'sqlite3', '~> 1.6'
gem 'erubi', '~> 1.12'
group :test do group :test do
gem 'cucumber', '~> 9.0'
gem "cucumber", "~> 9.0" gem 'capybara', '~> 3.39'
gem "capybara", "~> 3.39" gem 'rspec', '~> 3.12'
gem "rspec", "~> 3.12" gem 'selenium-webdriver', '~> 4.13'
gem "selenium-webdriver", "~> 4.13" gem 'simplecov'
gem "simplecov" gem 'rubocop', require: false
gem 'ruby_audit', '~> 2.2'
gem 'bundle-audit', '~> 0.1.0'
end end
gem "puma", "~> 6.4"
gem "rackup", "~> 2.1"
+100 -4
View File
@@ -3,8 +3,38 @@ GEM
specs: specs:
addressable (2.8.5) addressable (2.8.5)
public_suffix (>= 2.0.2, < 6.0) public_suffix (>= 2.0.2, < 6.0)
ast (2.4.2)
async (2.6.5)
console (~> 1.10)
fiber-annotation
io-event (~> 1.1)
timers (~> 4.1)
async-container (0.16.12)
async
async-io
async-http (0.61.0)
async (>= 1.25)
async-io (>= 1.28)
async-pool (>= 0.2)
protocol-http (~> 0.25.0)
protocol-http1 (~> 0.16.0)
protocol-http2 (~> 0.15.0)
traces (>= 0.10.0)
async-http-cache (0.4.3)
async-http (~> 0.56)
async-io (1.37.0)
async
async-pool (0.4.0)
async (>= 1.25)
base64 (0.1.1)
bigdecimal (3.1.4) bigdecimal (3.1.4)
build-environment (1.13.0)
builder (3.2.4) builder (3.2.4)
bundle-audit (0.1.0)
bundler-audit
bundler-audit (0.9.1)
bundler (>= 1.2.0, < 3)
thor (~> 1.0)
capybara (3.39.2) capybara (3.39.2)
addressable addressable
matrix matrix
@@ -14,6 +44,9 @@ GEM
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
regexp_parser (>= 1.5, < 3.0) regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2) xpath (~> 3.2)
console (1.23.2)
fiber-annotation
fiber-local
cucumber (9.0.2) cucumber (9.0.2)
builder (~> 3.2, >= 3.2.4) builder (~> 3.2, >= 3.2.4)
cucumber-ci-environment (~> 9.2, >= 9.2.0) cucumber-ci-environment (~> 9.2, >= 9.2.0)
@@ -41,18 +74,53 @@ GEM
diff-lcs (1.5.0) diff-lcs (1.5.0)
docile (1.4.0) docile (1.4.0)
erubi (1.12.0) erubi (1.12.0)
falcon (0.42.3)
async
async-container (~> 0.16.0)
async-http (~> 0.57)
async-http-cache (~> 0.4.0)
async-io (~> 1.22)
build-environment (~> 1.13)
bundler
localhost (~> 1.1)
openssl (~> 3.0)
process-metrics (~> 0.2.0)
protocol-rack (~> 0.1)
samovar (~> 2.1)
ffi (1.16.2) ffi (1.16.2)
fiber-annotation (0.2.0)
fiber-local (1.0.0)
io-event (1.3.3)
json (2.6.3)
language_server-protocol (3.17.0.3)
localhost (1.1.10)
mapping (1.1.1)
matrix (0.4.2) matrix (0.4.2)
mini_mime (1.1.5) mini_mime (1.1.5)
mini_portile2 (2.8.4) mini_portile2 (2.8.4)
multi_test (1.1.0) multi_test (1.1.0)
nio4r (2.5.9)
nokogiri (1.15.4) nokogiri (1.15.4)
mini_portile2 (~> 2.8.2) mini_portile2 (~> 2.8.2)
racc (~> 1.4) racc (~> 1.4)
openssl (3.2.0)
parallel (1.23.0)
parser (3.2.2.4)
ast (~> 2.4.1)
racc
process-metrics (0.2.1)
console (~> 1.8)
samovar (~> 2.1)
protocol-hpack (1.4.2)
protocol-http (0.25.0)
protocol-http1 (0.16.0)
protocol-http (~> 0.22)
protocol-http2 (0.15.1)
protocol-hpack (~> 1.4)
protocol-http (~> 0.18)
protocol-rack (0.2.6)
protocol-http (~> 0.23)
rack (>= 1.0)
public_suffix (5.0.3) public_suffix (5.0.3)
puma (6.4.0)
nio4r (~> 2.0)
racc (1.7.1) racc (1.7.1)
rack (3.0.8) rack (3.0.8)
rack-test (2.1.0) rack-test (2.1.0)
@@ -60,6 +128,7 @@ GEM
rackup (2.1.0) rackup (2.1.0)
rack (>= 3) rack (>= 3)
webrick (~> 1.8) webrick (~> 1.8)
rainbow (3.1.1)
regexp_parser (2.8.1) regexp_parser (2.8.1)
rexml (3.2.6) rexml (3.2.6)
roda (3.72.0) roda (3.72.0)
@@ -77,7 +146,27 @@ GEM
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0) rspec-support (~> 3.12.0)
rspec-support (3.12.1) rspec-support (3.12.1)
rubocop (1.57.1)
base64 (~> 0.1.1)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
parser (>= 3.2.2.4)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.28.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.29.0)
parser (>= 3.2.1.0)
ruby-progressbar (1.13.0)
ruby_audit (2.2.0)
bundler-audit (~> 0.9.0)
rubyzip (2.3.2) rubyzip (2.3.2)
samovar (2.2.0)
console (~> 1.0)
mapping (~> 1.0)
selenium-webdriver (4.13.1) selenium-webdriver (4.13.1)
rexml (~> 3.2, >= 3.2.5) rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0) rubyzip (>= 1.2.2, < 3.0)
@@ -94,7 +183,11 @@ GEM
mini_portile2 (~> 2.8.0) mini_portile2 (~> 2.8.0)
sys-uname (1.2.3) sys-uname (1.2.3)
ffi (~> 1.1) ffi (~> 1.1)
thor (1.3.0)
tilt (2.3.0) tilt (2.3.0)
timers (4.3.5)
traces (0.11.1)
unicode-display_width (2.5.0)
webrick (1.8.1) webrick (1.8.1)
websocket (1.2.10) websocket (1.2.10)
xpath (3.2.0) xpath (3.2.0)
@@ -104,13 +197,16 @@ PLATFORMS
x86_64-freebsd-13 x86_64-freebsd-13
DEPENDENCIES DEPENDENCIES
bundle-audit (~> 0.1.0)
capybara (~> 3.39) capybara (~> 3.39)
cucumber (~> 9.0) cucumber (~> 9.0)
erubi (~> 1.12) erubi (~> 1.12)
puma (~> 6.4) falcon (~> 0.42.3)
rackup (~> 2.1) rackup (~> 2.1)
roda (~> 3.72) roda (~> 3.72)
rspec (~> 3.12) rspec (~> 3.12)
rubocop
ruby_audit (~> 2.2)
selenium-webdriver (~> 4.13) selenium-webdriver (~> 4.13)
sequel (~> 5.72) sequel (~> 5.72)
simplecov simplecov
Vendored
+93 -10
View File
@@ -1,5 +1,7 @@
def buildArtifact = true
pipeline { pipeline {
agent any agent { label 'ruby && freebsd' }
environment { environment {
DB_NAME = 'url_shortener.db' DB_NAME = 'url_shortener.db'
@@ -9,8 +11,10 @@ pipeline {
steps { steps {
sh ''' #!/usr/local/bin/bash sh ''' #!/usr/local/bin/bash
rbenv local 3.2.2 rbenv local 3.2.2
echo "ENV[\\\"APP_SESSION_SECRET\\\"] ||= $(ruby -rsecurerandom -e 'puts SecureRandom.base64(64).inspect()')" > .env.rb echo "# frozen_string_literal: true\n" > .env.rb
echo "ENV[\\\"DB_NAME\\\"] ||= \\\"${DB_NAME}\\\"" >> .env.rb echo "ENV['APP_SESSION_SECRET'] ||= '$(ruby -rsecurerandom -e 'puts SecureRandom.base64(64)')'" >> .env.rb
echo "ENV['DB_NAME'] ||= '${DB_NAME}'" >> .env.rb
cat .env.rb
''' '''
} }
} }
@@ -20,13 +24,60 @@ pipeline {
sh 'sequel -m db/migrations sqlite://db/${DB_NAME}' sh 'sequel -m db/migrations sqlite://db/${DB_NAME}'
} }
} }
stage('Audit Dependencies') {
steps {
catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') {
script {
try {
sh 'bundle exec ruby-audit check'
sh 'bundle exec bundle-audit check >> audit.html'
} catch (e) {
script {
buildArtifact = false
}
}
}
}
}
}
stage('Code Linting') {
steps {
catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') {
script {
try {
sh 'bundle exec rubocop --format html --out rubocop.html'
} catch (e) {
script {
buildArtifact = false
}
}
}
}
}
}
stage('Run tests') { stage('Run tests') {
steps { steps {
catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') { catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') {
sh 'cucumber features --format html --out cucumber.html' script {
try {
sh 'bundle exec cucumber features --format html --out cucumber.html'
} catch (e) {
script {
buildArtifact = false
}
}
}
} }
catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') { catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') {
sh 'rspec spec --format html --out spec.html' script {
try {
sh 'bundle exec rspec spec --format html --out spec.html'
} catch (e) {
script {
buildArtifact = false
}
}
}
} }
} }
} }
@@ -37,23 +88,31 @@ pipeline {
alwaysLinkToLastBuild: false, alwaysLinkToLastBuild: false,
keepAll: true, keepAll: true,
reportDir: '.', reportDir: '.',
reportFiles: 'cucumber.html, spec.html, coverage/index.html', reportFiles: 'rubocop.html, audit.html, cucumber.html, spec.html, coverage/index.html',
reportName: 'Test Results', reportName: 'Test Results',
reportTitles: 'Cucumber Results, RSpec Results, Test Coverage']) reportTitles: 'Rubocop Results, Bundler Audit Results, Cucumber Results, RSpec Results, Test Coverage'])
} }
} }
stage('Build production deliverable') { stage('Build production deliverable') {
when {
expression {
buildArtifact
}
}
steps { steps {
catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') { catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') {
sh ''' #!/usr/local/bin/bash sh ''' #!/usr/local/bin/bash
ZIP_FILE="url-shortener_$(date "+%Y-%m-%d_%H-%M-%S").tgz" ZIP_FILE="url-shortener_${BRANCH_NAME}_$(cat VERSION | cut -d"'" -f2).tgz"
CUR_DIR=$(pwd) CUR_DIR=$(pwd)
bundle config set --local without 'test'
bundle config set --local path "vendor"
bundle install
mkdir -p /tmp/url-shortener mkdir -p /tmp/url-shortener
cp -R * /tmp/url-shortener cp -R * /tmp/url-shortener
cp .env.rb /tmp/url-shortener/ cp .env.rb /tmp/url-shortener/
cp .ruby-version /tmp/url-shortener/ cp .ruby-version /tmp/url-shortener/
cd /tmp/url-shortener cd /tmp/url-shortener
rm -rf features spec coverage db/*.db .git* Jenkinsfile *.html rm -rf features spec coverage db/*.db .git* Jenkinsfile *.html .rubocop.yml
cd /tmp cd /tmp
tar -czvf $ZIP_FILE url-shortener/ tar -czvf $ZIP_FILE url-shortener/
mv /tmp/$ZIP_FILE $CUR_DIR/ mv /tmp/$ZIP_FILE $CUR_DIR/
@@ -62,10 +121,34 @@ pipeline {
archiveArtifacts artifacts: '*.tgz' archiveArtifacts artifacts: '*.tgz'
} }
} }
stage('Generate SHA256 Hash and Upload to Artifact Repository') {
environment {
ARTIFACTS_KEY = credentials('artifactor-key')
}
steps {
sshagent(['artifactor-key']) {
sh ''' #!/usr/local/bin/bash
UUID=$(uuidgen -r)
VERSION=$(cat VERSION | cut -d"'" -f2)
BRANCH=${BRANCH_NAME}
ZIP_FILE="url-shortener_${BRANCH_NAME}_$(cat VERSION | cut -d"'" -f2).tgz"
sha256 $ZIP_FILE >> SHA256.sig
ssh artifactor@artifacts mkdir -p projects/url-shortener/$BRANCH/$VERSION/$UUID
rsync SHA256.sig artifactor@artifacts:projects/url-shortener/$BRANCH/$VERSION/$UUID/
rsync $ZIP_FILE artifactor@artifacts:projects/url-shortener/$BRANCH/$VERSION/$UUID/
'''
}
}
}
stage('Clean up deliverable') { stage('Clean up deliverable') {
when {
expression {
buildArtifact
}
}
steps { steps {
sh 'rm -rf /tmp/url-shortener' sh 'rm -rf /tmp/url-shortener'
sh 'rm -rf *.tgz' sh 'rm SHA256.sig'
} }
} }
} }
+12 -8
View File
@@ -1,11 +1,11 @@
# simple url shortener # simple url shortener
this project is a simple solution to build out a URL shortener using this project is a simple solution to build out a URL shortener using
Roda, Falcon, Sequel, and SQLite Roda, Puma, Sequel, and SQLite on a FreeBSD Jail
the point of this project is to quickly build something and work on continuous deployment while making small refinements to the functional pieces. the point of this project is to quickly build something and work on continuous deployment while making small refinements to the functional pieces.
the outside pieces of software that this project relies on are sqlite3 and pkgconf. the outside pieces of software that this project relies on are rbenv, ruby-build, sqlite3, and pkgconf.
@@ -14,9 +14,9 @@ first you have to install the dependencies:
`bundle install` `bundle install`
if you want the development group included run this first: if you want to skip the test dependencies:
`bundle config set --local with 'development'` `bundle config set --local without 'test'`
then create a .env.rb file in the root directory that contains the following ENV attributes: then create a .env.rb file in the root directory that contains the following ENV attributes:
@@ -24,15 +24,19 @@ then create a .env.rb file in the root directory that contains the following ENV
``` ```
ENV["APP_SESSION_SECRET"] = {output of a random 64 byte secret} ENV["APP_SESSION_SECRET"] = {output of a random 64 byte secret}
ENV["DB_NAME"] = {db file name} ENV["DB_NAME"] = {DB_NAME}
``` ```
after the dependencies are installed, you have to create the db after the dependencies are installed, you have to create the db and schema
`sequel -m db/migrations sqlite://db/{DB_NAME}` `sequel -m db/migrations sqlite://db/{DB_NAME}`
to start the application with Falcon: to start the application with Puma:
`rackup -o {ip address} -p {port} -s falcon` `rackup -o {ip address} -p {port}`
to run it as a daemon:
`rackup -o {ip address} -p {port} -D`
+1
View File
@@ -0,0 +1 @@
VERSION = '1.0.1'
+33 -30
View File
@@ -1,9 +1,12 @@
# frozen_string_literal: true
require 'roda' require 'roda'
require 'securerandom' require 'securerandom'
require 'json' require 'json'
require 'sequel' require 'sequel'
require 'open-uri' require 'open-uri'
# URL Shortener App class
class App < Roda class App < Roda
plugin :sessions, secret: ENV.delete('APP_SESSION_SECRET') plugin :sessions, secret: ENV.delete('APP_SESSION_SECRET')
plugin :render, escape: true plugin :render, escape: true
@@ -11,92 +14,92 @@ class App < Roda
plugin :json_parser plugin :json_parser
plugin :request_headers plugin :request_headers
DB = Sequel.sqlite("db/#{ENV['DB_NAME']}") DB = Sequel.sqlite("db/#{ENV.fetch('DB_NAME', nil)}")
links = DB[:links] links = DB[:links]
route do |r| route do |r|
r.root do r.root do
@message = flash['message'] || "Enter a URL" @message = flash['message'] || 'Enter a URL'
view :home view :home
end end
r.get String do |url_code| r.get String do |url_code|
link = links.filter(:code => url_code) link = links.filter(code: url_code)
r.redirect link.first[:url] unless link.first.nil? r.redirect link.first[:url] unless link.first.nil?
@message = "Link #{url_code} doesn't exist" @message = "Link #{url_code} doesn't exist"
response.status = 404 response.status = 404
view :home view :home
end end
r.post "create" do r.post 'create' do
url = r.params['url'] url = r.params['url']
if url.nil? or url.empty? if url.nil? || url.empty?
flash['message'] = "Please enter a valid URL"; flash['message'] = 'Please enter a valid URL'
r.redirect '/' r.redirect '/'
end end
begin begin
OpenURI.open_uri(url) OpenURI.open_uri(url)
rescue URI::BadURIError rescue URI::BadURIError
flash['message'] = "Invalid URL" flash['message'] = 'Invalid URL'
r.redirect '/' r.redirect '/'
rescue OpenURI::HTTPError rescue OpenURI::HTTPError
flash['message'] = "URL not found" flash['message'] = 'URL not found'
r.redirect '/' r.redirect '/'
rescue SocketError => e rescue SocketError
flash['message'] = "URL does not resolve" flash['message'] = 'URL does not resolve'
r.redirect '/' r.redirect '/'
end end
if links.filter(:url => url).first.nil? if links.filter(url:).first.nil?
code = SecureRandom.urlsafe_base64 4 code = SecureRandom.urlsafe_base64 4
links.insert(url: url, code: code) links.insert(url:, code:)
@message = "Link created" @message = 'Link created'
end end
code = links.filter(:url => url).first[:code] code = links.filter(url:).first[:code]
@message ||= "Link exists" @message ||= 'Link exists'
@new_link = 'http://' + request.env['HTTP_HOST'] + '/' + code @new_link = "http://#{request.env['HTTP_HOST']}/#{code}"
view :create view :create
end end
r.on "links" do r.on 'links' do
r.post do r.post do
if 'application/json' != r.headers['CONTENT_TYPE'] if r.headers['CONTENT_TYPE'] != 'application/json'
response.status = 400 response.status = 400
return {message: "not a valid json request"}.to_json return { message: 'not a valid json request' }.to_json
end end
url = r.params['url'] url = r.params['url']
if url.nil? if url.nil?
response.status = 400 response.status = 400
return {message: "missing url parameter"}.to_json return { message: 'missing url parameter' }.to_json
end end
if url.empty? if url.empty?
response.status = 400 response.status = 400
return {message: "invalid url parameter"}.to_json return { message: 'invalid url parameter' }.to_json
end end
begin begin
OpenURI.open_uri(url) OpenURI.open_uri(url)
rescue URI::BadURIError rescue URI::BadURIError
response.status = 400 response.status = 400
return {message: "invalid url parameter"}.to_json return { message: 'invalid url parameter' }.to_json
rescue OpenURI::HTTPError rescue OpenURI::HTTPError
response.status = 400 response.status = 400
return {message: "url not found"}.to_json return { message: 'url not found' }.to_json
rescue SocketError => e rescue SocketError
response.status = 400 response.status = 400
return {message: "url does not resolve"}.to_json return { message: 'url does not resolve' }.to_json
end end
if links.filter(:url => url).first.nil? if links.filter(url:).first.nil?
code = SecureRandom.urlsafe_base64 4 code = SecureRandom.urlsafe_base64 4
links.insert(url: url, code: code) links.insert(url:, code:)
end end
code = links.filter(:url => url).first[:code] code = links.filter(url:).first[:code]
@new_link = 'http://' + request.env['HTTP_HOST'] + '/' + code @new_link = "http://#{request.env['HTTP_HOST']}/#{code}"
return {url: url, code: code, link: @new_link}.to_json return { url:, code:, link: @new_link }.to_json
end end
end end
end end
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
require './.env' require './.env'
require './app' require './app'
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
Sequel.migration do Sequel.migration do
change do change do
create_table :links do create_table :links do
+5 -7
View File
@@ -1,9 +1,10 @@
# frozen_string_literal: true
# BEFORE # BEFORE
Before('@db-test') do Before('@db-test') do
@links = Sequel.sqlite("db/#{ENV['DB_NAME']}")[:links] @links = Sequel.sqlite("db/#{ENV.fetch('DB_NAME', nil)}")[:links]
end end
# GIVEN # GIVEN
Given('I visit the {string} page') do |string| Given('I visit the {string} page') do |string|
@@ -11,14 +12,13 @@ Given('I visit the {string} page') do |string|
end end
Given('A link already exists with the url {string}') do |string| Given('A link already exists with the url {string}') do |string|
@links.insert(url: string, code: "aaaaaa") @links.insert(url: string, code: 'aaaaaa')
end end
Given('A link already exists with the url {string} and code {string}') do |url, code| Given('A link already exists with the url {string} and code {string}') do |url, code|
@links.insert(url: url, code: code) @links.insert(url:, code:)
end end
# WHEN # WHEN
When('I click the {string} button') do |string| When('I click the {string} button') do |string|
@@ -33,7 +33,6 @@ When('I visit the {string} location') do |string|
visit string visit string
end end
# THEN # THEN
Then('I should see text {string}') do |string| Then('I should see text {string}') do |string|
@@ -66,7 +65,6 @@ Then('The status code should be {int}') do |code|
page.status_code.should eq(code) page.status_code.should eq(code)
end end
# AFTER # AFTER
After('@db-test') do After('@db-test') do
+9 -7
View File
@@ -1,25 +1,27 @@
# frozen_string_literal: true
require 'simplecov' require 'simplecov'
SimpleCov.start SimpleCov.start
require_relative '../../.env' require_relative '../../.env'
ENV["DB_NAME"] = "test_#{ENV["DB_NAME"]}" ENV['DB_NAME'] = "test_#{ENV.fetch('DB_NAME', nil)}"
require_relative '../../app' require_relative '../../app'
require 'rubygems' require 'rubygems'
require 'roda' require 'roda'
require 'sequel' require 'sequel'
require 'capybara' require 'capybara'
require 'capybara/dsl' require 'capybara/dsl'
require 'capybara/cucumber'
require 'rspec' require 'rspec'
###
require 'rspec/expectations'
require 'rspec/matchers'
# DB initialization # DB initialization
Sequel.extension :migration Sequel.extension :migration
Sequel.sqlite("db/#{ENV['DB_NAME']}") do |db| Sequel.sqlite("db/#{ENV.fetch('DB_NAME', nil)}") do |db|
Sequel::Migrator.apply(db, "db/migrations") Sequel::Migrator.apply(db, 'db/migrations')
end end
# attach app to Capybara # attach app to Capybara
Capybara.app = App Capybara.app = App
include Capybara::DSL
include RSpec::Expectations
include RSpec::Matchers
+17 -15
View File
@@ -1,8 +1,10 @@
# frozen_string_literal: true
require 'simplecov' require 'simplecov'
SimpleCov.start SimpleCov.start
require_relative '../.env' require_relative '../.env'
ENV["DB_NAME"] = "test_#{ENV["DB_NAME"]}" ENV['DB_NAME'] = "test_#{ENV.fetch('DB_NAME', nil)}"
require_relative '../app' require_relative '../app'
require 'rubygems' require 'rubygems'
require 'roda' require 'roda'
@@ -12,27 +14,27 @@ require 'rack/test'
# DB initialization # DB initialization
Sequel.extension :migration Sequel.extension :migration
Sequel.sqlite("db/#{ENV['DB_NAME']}") do |db| Sequel.sqlite("db/#{ENV.fetch('DB_NAME', nil)}") do |db|
Sequel::Migrator.apply(db, "db/migrations") Sequel::Migrator.apply(db, 'db/migrations')
end end
def app def app
App App
end end
describe "Submit API request to create new link" do describe 'Submit API request to create new link' do
include Rack::Test::Methods include Rack::Test::Methods
before :each do before :each do
@links = Sequel.sqlite("db/#{ENV['DB_NAME']}")[:links] @links = Sequel.sqlite("db/#{ENV.fetch('DB_NAME', nil)}")[:links]
end end
after :each do after :each do
@links.delete @links.delete
end end
it "should return link data in json format when a valid url is submitted" do it 'should return link data in json format when a valid url is submitted' do
data = { data = {
url: 'http://google.com' url: 'http://google.com'
} }
post('/links', data.to_json, "CONTENT_TYPE" => "application/json") post('/links', data.to_json, 'CONTENT_TYPE' => 'application/json')
expect(last_response).to be_ok expect(last_response).to be_ok
response_json = JSON.parse(last_response.body) response_json = JSON.parse(last_response.body)
expect(response_json['url']).to eq(data[:url]) expect(response_json['url']).to eq(data[:url])
@@ -44,16 +46,15 @@ describe "Submit API request to create new link" do
data = { data = {
url: '' url: ''
} }
post('/links', data.to_json, "CONTENT_TYPE" => "application/json") post('/links', data.to_json, 'CONTENT_TYPE' => 'application/json')
expect(last_response.status).to eq(400) expect(last_response.status).to eq(400)
response_json = JSON.parse(last_response.body) response_json = JSON.parse(last_response.body)
expect(response_json['message']).to eq('invalid url parameter') expect(response_json['message']).to eq('invalid url parameter')
end end
it "should return with a 400 status and 'missing url parameter' message when an empty url is submitted" do it "should return with a 400 status and 'missing url parameter' message when an empty url is submitted" do
data = { data = {}
} post('/links', data.to_json, 'CONTENT_TYPE' => 'application/json')
post('/links', data.to_json, "CONTENT_TYPE" => "application/json")
expect(last_response.status).to eq(400) expect(last_response.status).to eq(400)
response_json = JSON.parse(last_response.body) response_json = JSON.parse(last_response.body)
expect(response_json['message']).to eq('missing url parameter') expect(response_json['message']).to eq('missing url parameter')
@@ -63,7 +64,7 @@ describe "Submit API request to create new link" do
data = { data = {
url: 'not-an-url' url: 'not-an-url'
} }
post('/links', data.to_json, "CONTENT_TYPE" => "application/json") post('/links', data.to_json, 'CONTENT_TYPE' => 'application/json')
expect(last_response.status).to eq(400) expect(last_response.status).to eq(400)
response_json = JSON.parse(last_response.body) response_json = JSON.parse(last_response.body)
expect(response_json['message']).to eq('invalid url parameter') expect(response_json['message']).to eq('invalid url parameter')
@@ -73,7 +74,7 @@ describe "Submit API request to create new link" do
data = { data = {
url: 'http://google.com/example' url: 'http://google.com/example'
} }
post('/links', data.to_json, "CONTENT_TYPE" => "application/json") post('/links', data.to_json, 'CONTENT_TYPE' => 'application/json')
expect(last_response.status).to eq(400) expect(last_response.status).to eq(400)
response_json = JSON.parse(last_response.body) response_json = JSON.parse(last_response.body)
expect(response_json['message']).to eq('url not found') expect(response_json['message']).to eq('url not found')
@@ -83,13 +84,14 @@ describe "Submit API request to create new link" do
data = { data = {
url: 'http://bad.tld' url: 'http://bad.tld'
} }
post('/links', data.to_json, "CONTENT_TYPE" => "application/json") post('/links', data.to_json, 'CONTENT_TYPE' => 'application/json')
expect(last_response.status).to eq(400) expect(last_response.status).to eq(400)
response_json = JSON.parse(last_response.body) response_json = JSON.parse(last_response.body)
expect(response_json['message']).to eq('url does not resolve') expect(response_json['message']).to eq('url does not resolve')
end end
it "should return with a 400 status and 'not a valid json request' message when a request is made with the wrong content type header" do it "should return with a 400 status and 'not a valid json request'
message when a request is made with the wrong content type header" do
data = { data = {
url: 'http://google.com' url: 'http://google.com'
} }