node.js download file from aws s3 bucket via http request with AWS Signature Version 4

Main codes

function getYMD(d) {
    let r = d.getFullYear().toString();
    let m = d.getUTCMonth() + 1;
    if (m < 10)
      m = "0" + m.toString();
    else
      m = m.toString();
    r = r + m;
    let day = d.getUTCDate();
    if (day < 10)
      day = "0" + day.toString();
    else
      day = day.toString();
    r = r + day;
    return r;
}

function getTZ(d) {
    let r = d.getFullYear().toString();
    let m = d.getUTCMonth() + 1;
    if (m < 10)
      m = "0" + m.toString();
    else
      m = m.toString();
    r = r + m;
    let day = d.getUTCDate();
    if (day < 10)
      day = "0" + day.toString();
    else
      day = day.toString();
    r = r + day + "T";
    let h = d.getUTCHours();
    if (h < 10)
      h = "0" + h.toString();
    else
      h = h.toString();
    r = r + h;
    let min = d.getUTCMinutes();
    if (min < 10)
      min = "0" + min.toString();
    else
      min = min.toString();
    r = r + min;
    let s = d.getUTCSeconds();
    if (s < 10)
      s = "0" + s.toString();
    else
      s = s.toString();
    r = r + s;
    r = r + "Z";
    return r;
}

const crypto = require('crypto');
function Hmac(key, string){
    const hmac = crypto.createHmac('sha256', key);
    hmac.end(string);
    return hmac.read();
}

function Signature(date, region, service, toSign) {
    let dateKey = Hmac('AWS4' + AWS_SECRETACCESS_KEY, date);
    let dateRegionKey = Hmac(dateKey, region);
    let dateRegionServiceKey = Hmac(dateRegionKey, service);
    let signingKey = Hmac(dateRegionServiceKey, 'aws4_request');
    let signature = Hmac(signingKey, toSign).toString('hex');
    return signature;
}

function stringToSign(timeStamp, scope, canonicalRequest) {
    var signParts = [];
    signParts.push('AWS4-HMAC-SHA256');
    signParts.push(timeStamp);
    signParts.push(scope);
    signParts.push(crypto.createHash('sha256').update(canonicalRequest).digest(
'hex'));
    var result = signParts.join('\n');
    console.log('string to sign');
    console.log(result);
    return result;
};

function canonicalRequest(method, uri, queryString, headers, signedHeaders, has
hedPayload) {
  var canonicalParts = [];
  canonicalParts.push(method);
  canonicalParts.push(uri);
  canonicalParts.push(queryString);
  canonicalParts.push(headers);
  canonicalParts.push(signedHeaders);
  canonicalParts.push(hashedPayload);
  var result = canonicalParts.join('\n');
  console.log('canonical request');
  console.log(result);
  return result;
}

function download(filepath) {
    let filename = filepath;
    let position = filename.lastIndexOf('/');
    if (position != -1) {
      filename = filename.substr(position+1);
    }
    let d = new Date();
    let dtz = getTZ(d);
    let dymd = getYMD(d);
    let scope = dymd + '/' + AWS_REGION + '/' + AWS_SERVICE + '/aws4_request';
    let uri = filepath;
    let queryString = "X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=" + en
codeURIComponent(AWS_KEY + "/" + scope) + "&X-Amz-Date=" + dtz + "&X-Amz-Expire
s=400&X-Amz-SignedHeaders=host&response-content-disposition=attachment%3Bfilena
me%3D" + filename;
    let headers = "host:s3.amazonaws.com";
    let signedHeaders = "";
    let hashedPayload = "host\nUNSIGNED-PAYLOAD";
    let request = canonicalRequest('GET', uri, queryString, headers, signedHead
ers, hashedPayload);
    let signature = Signature(dymd, AWS_REGION, AWS_SERVICE, stringToSign(dtz,
scope, request));
    let url = 'https://s3.amazonaws.com' + filepath + '?response-content-dispos
ition=attachment;filename=' + filename + '&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-A
mz-Credential=' + AWS_KEY + "/" + scope + '&X-Amz-Date=' + dtz + '&X-Amz-Expire
s=400&X-Amz-SignedHeaders=host&X-Amz-Signature=' + signature;
    console.log(url);
    var link = document.createElement('a');
    link.innerHTML = 'download';
    link.href = url;
    document.body.appendChild(link);
}

Usage

const AWS_KEY = 'your aws key';
const AWS_SECRETACCESS_KEY = 'your aws secret access key';
const AWS_REGION = 'your bucket region'; // default us-east-1
const AWS_SERVICE = 's3';

download('/yourbucket/objectkey');

examle

download

Refers

GET Object
Signing AWS Requests with Signature Version 4
Authenticating Requests: Using Query Parameters (AWS Signature Version 4)
Key point:
?response-content-disposition=attachment;filename=objectname
Using this header, browser will open an save dialog once the down load url is correct signed.
You will get a url link like:
https://s3.amazonaws.com/bucket/objectkey?response-content-disposition=attachment;filename=objectname&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAJWWH7EGSUBWE34IQ/20181218/us-east-1/s3/aws4_request&X-Amz-Date=20181218T053905Z&X-Amz-Expires=400&X-Amz-SignedHeaders=host&X-Amz-Signature=71d82d4a99338d777bfa3517315d34cad94619a9ca891222808e16dd02de7835

easy tab component, pure js + css, without react


This is tab example 1
Passion Organization Content Engagement this is passion pannel this is organization pannel this is engagement pannel
This is tab example 2
Passion Organization Content Engagement this is passion pannel 2 this is organization pannel 2 this is engagement pannel 2
end test
This is haha node

styles

<style>
.container {
    padding-right: 15px;
    padding-left: 15px;
    margin-right: auto;
    margin-left: auto;
}
.tabs {
    padding-left: 0;
    margin-bottom: 0;
    list-style: none; 
}
ol, ul {
    margin-top: 0;
    margin-bottom: 10px;
}
.tabs > li {    
    margin-bottom: -1px;
    position: relative;
    display: inline-block; 
 padding: 6px 12px;
 border-radius: 5px 5px 0 0;
 background-color: #aac4bd;
 border: 2px solid #7ea299;
 margin-right: 2px;
 color: #fff;
 cursor: pointer;
}
.tabs > li.active {
 background-color: #fbfbfc;
 border-bottom: 0;
 padding-bottom: 8px;
 color: #7ea299; 
}
.tabs > li:hover {
 background-color: #fbfbfc;
 color: #7ea299;  
}
.tabcontent {
 border: 2px solid #7ea299;
 -webkit-box-shadow: .055rem .055rem 1.11rem hsla(0,0%,8%,.27);
 box-shadow: .055rem .055rem 1.11rem hsla(0,0%,8%,.27);
 background-color: #fbfbfc;
 margin: -1px 0 0;
 padding: 4px; 
}
.tabcontent > .panel {
    display: none;
}
.tabcontent > .active {
    display: block;
}
</style>

use example

<div>
This is tab example 1
</div>
<Tabs>
<TabList>
  <Tab>Passion</Tab>
  <Tab>Organization Content</Tab>
  <Tab>Engagement</Tab>
</TabList>
<TabPanel>
  this is passion pannel
</TabPanel> 
<TabPanel>
this is organization pannel
</TabPanel>
<TabPanel>
this is engagement pannel
</TabPanel> 
</Tabs>
<div>
This is tab example 2
</div>
<Tabs>
<TabList>
  <Tab>Passion</Tab>
  <Tab>Organization Content</Tab>
  <Tab class='active'>Engagement</Tab>
</TabList>
<TabPanel>
  this is passion pannel 2
</TabPanel> 
<TabPanel>
this is organization pannel 2
</TabPanel>
<TabPanel>
this is engagement pannel 2
</TabPanel> 
</Tabs>
<div>
end test
</div>

javascript

<script type="text/javascript">
document.querySelectorAll('Tabs').forEach(function(tabs) {
  let pn = tabs.parentNode;
  let ct = document.createElement('div');
  ct.className = 'container';
  let ul = document.createElement('ul');
  ul.className = 'tabs';
  ct.appendChild(ul);
  let ai = 0;
  let index = 0;
  tabs.querySelectorAll('Tab').forEach(function(tab) {
    let li = document.createElement('li');
 li.className = tab.className;
 if (li.className.indexOf('active') != -1) {
   ai = index;
 }
 li.innerText = tab.innerText;
 li.id = index;
 li.onclick = function(e) {
   e = e || window.event;
      let target = e.target || e.srcElement;
   if (target.className.indexOf('active') != -1)
     return;  
   let ft = target.parentNode.querySelector('.active');
   ft.className = ft.className.replace('active', '');
   target.className = target.className + " active";
   let cp = target.parentNode.parentNode.querySelector('.tabcontent .active');
      cp.className = cp.className.replace('active', '');
   cp = target.parentNode.parentNode.querySelector('.tabcontent #panel'+target.id);
      cp.className = cp.className + " active";
   // target.parentNode.parentNode.style.display = 'block';
 }
    ul.appendChild(li); 
 index = index+1;
  });
  if (ai == 0) {
    if (ul.firstChild.className.indexOf('active') == -1) {
   ul.firstChild.className += ' active';
 }
  }
  let tc = document.createElement('div');
  tc.className = 'tabcontent';
  ct.appendChild(tc);
  index = 0;
  tabs.querySelectorAll('TabPanel').forEach(function(tabpanel) {
    let pl = document.createElement('div');
 pl.className = 'panel';
 pl.id = 'panel'+index;
 if (index == ai) {
   pl.className += ' active';
 }
 pl.innerHTML = tabpanel.innerHTML;
 tc.appendChild(pl);
 index = index+1;
  });
  pn.insertBefore(ct, tabs);
  pn.removeChild(tabs);
});
</script>

How to Record video use getUserMedia and MediaRecorder API

example


sample codes

  <div>
    <video id='camera'></video>
  </div>
  <script type='text/javascript'>
    var p = navigator.mediaDevices.getUserMedia({ audio: true, video: true });
    p.then(function(mediaStream) {
      var video = document.querySelector('video');
      video.src = window.URL.createObjectURL(mediaStream);
      video.onloadedmetadata = function() {
        video.muted = true;
        video.play();
      }
      var mediaRecorder = new MediaRecorder(mediaStream);
      var chunks = [];
      mediaRecorder.ondataavailable = function(e) {
        chunks.push(e.data);
      }
      mediaRecorder.onstop = function() {
        var blob = new Blob(chunks, {'type' : 'video/webm'});
        chunks = [];
        var hyperlink = document.createElement('a');
        hyperlink.href = URL.createObjectURL(blob);
        video.src = hyperlink.href;
        video.muted = false;
        video.controls = true;
        hyperlink.download = 'record.mp4';
        hyperlink.style = 'display:none;opacity:0;color:transparent;';
        (document.body || document.documentElement).appendChild(hyperlink);
if (typeof hyperlink.click === 'function') {
  hyperlink.click();
} else {
            hyperlink.target = '_blank';
            hyperlink.dispatchEvent(new MouseEvent('click', {
                view: window,
                bubbles: true,
                cancelable: true
            }));
        }     
    }
      mediaRecorder.start();      
      setTimeout(function() {
        mediaRecorder.stop();
        video.src = '';
        video.muted = false;
        video.stop();
      }, 10000);
    });
</script>

Ghost publishing platform sources: how hbs template engine is set to its site app

core js codes

in core/server/services/themes/active.js:

        // Set the views and engine         siteApp.set('views', this.path);         siteApp.engine('hbs', engine.configure(this.partialsPath)); 

Refers

https://expressjs.com/en/guide/using-template-engines.html
https://docs.ghost.org/api/handlebars-themes/

sqlite3 show all tables of a database

show all tables of a database

open database file via sqlite3
sqlite3 content/data/ghost-dev.db
command to list all tables
.tables || .table

$sqlite3  content/data/ghost-dev.db       SQLite version 3.11.0 2016-02-15 17:29:24 Enter ".help" for usage hints. sqlite> .tables accesstokens            migrations              refreshtokens          api_keys                migrations_lock         roles                  app_fields              mobiledoc_revisions     roles_users            app_settings            permissions             sessions               apps                    permissions_apps        settings               brute                   permissions_roles       subscribers            client_trusted_domains  permissions_users       tags                   clients                 posts                   users                  integrations            posts_authors           webhooks               invites                 posts_tags             

show table structure

select * from tablename;

sqlite> select * from posts; id|uuid|title|slug|mobiledoc|html|comment_id|plaintext|feature_image|featured|page|status|locale|visibility|meta_title|meta_description|author_id|created_at|created_by|updated_at|updated_by|published_at|published_by|custom_excerpt|codeinjection_head|codeinjection_foot|og_image|og_title|og_description|twitter_image|twitter_title|twitter_description|custom_template 

Hack to ghost core: mount/add new apps/sites on your ghost server

Install ghost from source

First, you should know how to develop with ghost.

create a new react app under core/server folder

simple example
core/server$ mkdir newapp
core/server$ cd newapp
core/server/newapp$ 
Ghost/core/server/web/newapp$ ls
app.js  controller.js  index.js
Ghost/core/server/web/newapp$ cat index.js 
module.exports = require('./app');
Ghost/core/server/web/newapp$ cat app.js 
const express = require('express');
module.exports = function setupNewApp() {
    const newApp = express();
    newApp.get('*', require('./controller'));
    return newApp;
};
Ghost/core/server/web/newapp$ cat controller.js 
const path = require('path');

module.exports = function newAppController(req, res) {
    res.send('Welcome to new App!');
};

Add route for your new site/app

./core/server/app.js
    // Mount the  apps on the parentApp
  
    // ADMIN
    parentApp.use('/ghost', require('./admin')());

    // import new app/site
    parentApp.use('/newsite', require('./newapp')());
    
    // BLOG
    parentApp.use(require('./site')()); 

Notes

above are for ghost before 2.0 version.
for latest ghost, version 2.0,
The folder for new app should be "Ghost/core/server/web/newapp"
The hacked core js is Ghost/core/server/web/parent-app.js
The code for add route is same as above.

Work your new app

grunt dev
navigate to http://localhost:2368/newapp, you will get:
newapphacktoghost

fixed: embedded-redis: Unable to run on macOS Sonoma

Issue you might see below error while trying to run embedded-redis for your testing on your macOS after you upgrade to Sonoma. java.la...