Personal blog written from scratch using Node.js, Bootstrap, and MySQL. https://jrtechs.net
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

146 lines
8.2 KiB

  1. Over the last few weeks, Github has been making changes to its UI. I'm the most excited about the feature that enables you to "design" your profile using a readme file. This reminds me of Myspace, where everyone had creative freedom to customize their profiles. Github being a platform for developers, people are already finding innovative ways to utilize this feature.
  2. ![creation of a readme profile](media/github-readme/repository.png)
  3. To create one of these readme profiles, you just need to create a repository with the same name as your account, and the content you put in the base readme file will appear over your pinned repositories on your account.
  4. There is a curated list of example Readmes in the repository [awesome github profile readme](https://github.com/abhisheknaiidu/awesome-github-profile-readme).
  5. I was a big fan of [Athul's](https://github.com/athul/athul).
  6. The TLDR is that most developers use this space as an extended bio where they can showcase their skills and interests.
  7. ![example github readme](media/github-readme/athul.png)
  8. On my profile, I just included some lists of pertinent links for people to click on.
  9. The exciting part of my readme is the dynamic list of recent blog posts.
  10. ![Github readme jrtechs](media/github-readme/final.png)
  11. # Creating a Dynamic Profile
  12. The only secret to creating a dynamic component on your readme is to include an image that gets modified by some external service.
  13. It wasn't an arduous task to create an endpoint in NodeJS that generated an SVG image containing my most current blog posts.
  14. ```js
  15. routes.get('/recentSVG', (request, result) =>
  16. {
  17. sql.getRecentPosts(4).then((sqlData)=>
  18. {
  19. result.writeHead(200, {'Content-Type': 'image/svg+xml',
  20. 'Cache-Control': 'no-cache',
  21. 'Vary': 'Accept-Encoding'});
  22. var res = `
  23. <svg width="400" height="200" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  24. <g>
  25. <title>background</title>
  26. <rect x="-1" y="-1" width="808" height="202" id="canvas_background" fill="#fff"/>
  27. <g id="canvasGrid" display="none">
  28. <rect id="svg_1" width="100%" height="100%" x="0" y="0" stroke-width="0" fill="url(#gridpattern)"/>
  29. </g>
  30. </g>
  31. <g>
  32. <title>Jrtechs</title>
  33. <a xlink:href="https://jrtechs.net">
  34. <text fill="#498FBE" stroke="#000" stroke-width="0" stroke-opacity="null" x="36.5" y="40.5" id="svg_6" font-size="24" font-family="Oswald, sans-serif" text-anchor="start" xml:space="preserve" font-weight="bold">Recent Blog Posts</text>
  35. </a>
  36. <a xlink:href="${getURL(sqlData[0])}">
  37. <text fill="#000000" stroke="#000" stroke-width="0" stroke-opacity="null" x="65.5" y="73.5" id="svg_7" font-size="20" font-family="Oswald, sans-serif" text-anchor="start" xml:space="preserve" font-weight="normal">- ${sqlData[0].name}</text>
  38. </a>
  39. <a xlink:href="${getURL(sqlData[1])}">
  40. <text fill="#000000" stroke="#000" stroke-width="0" stroke-opacity="null" x="65.5" y="106.5" id="svg_7" font-size="20" font-family="Oswald, sans-serif" text-anchor="start" xml:space="preserve" font-weight="normal">- ${sqlData[1].name}</text>
  41. </a>
  42. <a xlink:href="${getURL(sqlData[2])}">
  43. <text fill="#000000" stroke="#000" stroke-width="0" stroke-opacity="null" x="65.5" y="139.5" id="svg_7" font-size="20" font-family="Oswald, sans-serif" text-anchor="start" xml:space="preserve" font-weight="normal">- ${sqlData[2].name}</text>
  44. </a>
  45. <a xlink:href="${getURL(sqlData[3])}">
  46. <text fill="#000000" stroke="#000" stroke-width="0" stroke-opacity="null" x="65.5" y="172.5" id="svg_7" font-size="20" font-family="Oswald, sans-serif" text-anchor="start" xml:space="preserve" font-weight="normal">- ${sqlData[3].name}</text>
  47. </a>
  48. </g>
  49. </svg>`;
  50. result.write(res);
  51. result.end();
  52. }).catch((err)=>
  53. {
  54. result.status(404).json({error: 404}).end();
  55. })
  56. });
  57. ```
  58. There is nothing too special about the SVG generation; however, there are a few caveats to point out.
  59. The first thing to note is that I went out of my way to include links within my SVG image. Although this might cajole you into thinking the links work on Github; when you embed an image on a Github readme, it will link to where the image is hosted on their caching server, it will not allow users to click on links in the image directly. Due to this nuisance, it is best to just define a hyperlink for the entire image, so the user doesn't get displayed a separate screen with only the image. Fortunately, you can do this in Github markdown by embedding it in some HTML with an anchoring tag.
  60. ```html
  61. <p align="left">
  62. <a href="https://jrtechs.net">
  63. <img src="https://jrtechs.net/api/recentSVG" alt="Recent Blog Posts"></img>
  64. </a>
  65. </p>
  66. ```
  67. Notice that I mentioned Github's caching server when talking about how the content got served.
  68. By default, GitHub will cache all images embedded in readmes that link to external sources using a service called [Camo](https://github.com/atmos/camo).
  69. This has several benefits to Github because users can embed HTTP content within an HTTPS page.
  70. The drawback to this cache is that it makes it rather tricky to serve dynamic images since they will just get cached by Github.
  71. This is an example curl request to Camo for a cached image:
  72. ```bash
  73. ┌─[jeff@matrix] - [/home/jeff] - [2020-07-25 10:39:57]
  74. └─[0] <> curl -I https://camo.githubusercontent.com/1dc7d7611b4dbdca4b54c6cacdd2823f1bce4fca/68747470733a2f2f6a7274656368732e6e65742f6170692f726563656e745356472e7376673f
  75. HTTP/1.1 200 OK
  76. Connection: keep-alive
  77. Content-Length: 2411
  78. Cache-Control: public, max-age=2678400
  79. Content-Security-Policy: default-src 'none'; img-src data:; style-src 'unsafe-inline'
  80. Content-Type: image/svg+xml
  81. Server: github-camo (62249a1c)
  82. Strict-Transport-Security: max-age=31536000; includeSubDomains
  83. X-Content-Type-Options: nosniff
  84. X-Frame-Options: deny
  85. X-Xss-Protection: 1; mode=block
  86. X-GitHub-Request-Id: 1640:4307:11CC6D:159A5E:5F1C443D
  87. Accept-Ranges: bytes
  88. Date: Sat, 25 Jul 2020 14:48:42 GMT
  89. Via: 1.1 varnish
  90. Age: 525
  91. X-Served-By: cache-mdw17377-MDW
  92. X-Cache: HIT
  93. X-Cache-Hits: 2
  94. X-Timer: S1595688523.694051,VS0,VE0
  95. Vary: Accept-Encoding
  96. X-Fastly-Request-ID: 7a621c4d10b3ee434e4f55a05b04a059c6a64fbe
  97. Timing-Allow-Origin: https://github.com
  98. ```
  99. Github enables you to clear the Camo cache for a single image using the curl PURGE command.
  100. This is a nice hack if you merely want to update an external image in your readme that got updated; however, you don't want to do this every time your dynamic component is updated. If the image is serving as a page visit counter, the purge call would have to be sent every time the image is fetched.
  101. ```bash
  102. ┌─[jeff@matrix] - [/home/jeff] - [2020-07-25 10:48:42]
  103. └─[0] <> curl -X PURGE https://camo.githubusercontent.com/1dc7d7611b4dbdca4b54c6cacdd2823f1bce4fca/68747470733a2f2f6a7274656368732e6e65742f6170692f726563656e745356472e7376673f
  104. { "status": "ok", "id": "4942-1591860663-32446227" }
  105. ```
  106. The cleaner route that I opted for was to simply disable caching by Camo using the Cache-Control header in my API route. Now when we curl the Camo cache for my image, it looks like this:
  107. ```
  108. ┌─[jeff@matrix] - [/home/jeff] - [2020-07-25 10:59:32]
  109. └─[0] <> curl -I https://camo.githubusercontent.com/1dc7d7611b4dbdca4b54c6cacdd2823f1bce4fca/68747470733a2f2f6a7274656368732e6e65742f6170692f726563656e745356472e7376673f
  110. ...
  111. Cache-Control: no-cache, no-store, private, must-revalidate
  112. Content-Security-Policy: default-src 'none'; img-src data:; style-src 'unsafe-inline'
  113. ...
  114. Age: 0
  115. X-Served-By: cache-mdw17330-MDW
  116. X-Cache: MISS
  117. X-Cache-Hits: 0
  118. ...
  119. ```
  120. Rather than pulling from a Cached version, Camo will go out and fetch the image from my website and serve it to the user viewing the readme file.
  121. That's all there is to say about my dynamic readme component. What is nice about this approach is that I can even embed this recent posts widget in things like this very blog post:
  122. ![recent posts](https://jrtechs.net/api/recentSVG)