tag:blogger.com,1999:blog-108932142024-03-13T09:10:19.405-07:00cawood's blog - geek literatureStephen Cawood's blog about technical writing, coding, and other important stuffStephen Cawoodhttp://www.blogger.com/profile/07948009840630937442noreply@blogger.comBlogger481125tag:blogger.com,1999:blog-10893214.post-44692272826786312322023-01-24T14:24:00.001-08:002023-01-24T14:29:33.409-08:00Breakpoints not working in VSCode with Unity 2021.3.16f1<p>I'm currently using the LTS build of Unity (2021.3.17f1) and Visual Studio Code (VSCode) to develop a 2D game with my kids. I'm using VSCode because I use it for pretty much all my development. I hope they reverse their decision and properly support it for Unity in the future.</p><p>When I first set up the project, breakpoints were working fine in VSCode. I'm not sure what broke along the way, but here I am a few days later and the debugger isn't working. I had to reinstall Mono at one point to solve the common VSCode issue that the project doesn't load properly.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgi-5Pvz3r7ezw37xLIi4B0KJa9TSaq4So-9rhR6kW_WCsQlM04AObAmgNPSTr0k_XI96nBdSWgtuYpHiRY0wcE7tb3i2e6fsTFk5LsvA8mxv9ZU6NyogX5YI9kBTjHMzv2wU8nZu91xVMrg6SrTXORXClw6Glz9RkTzMcwBIaIku9cuEvbQ/s1130/Screenshot%202023-01-24%20at%202.17.24%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="256" data-original-width="1130" height="103" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgi-5Pvz3r7ezw37xLIi4B0KJa9TSaq4So-9rhR6kW_WCsQlM04AObAmgNPSTr0k_XI96nBdSWgtuYpHiRY0wcE7tb3i2e6fsTFk5LsvA8mxv9ZU6NyogX5YI9kBTjHMzv2wU8nZu91xVMrg6SrTXORXClw6Glz9RkTzMcwBIaIku9cuEvbQ/w408-h103/Screenshot%202023-01-24%20at%202.17.24%20PM.png" width="408" /></a></div><div><br /></div><p>Here's what I had to do to get it working (see the initial setup link below). First, I clicked on the "Run and Debug" icon in VSCode, then I choose "create a .json launch file." After this, VSCode prompted me for which debugger to use. Of course, I chose "Unity Debugger." After that was all done, I clicked on Run and Debug in VSCode and the debugger started and attached to Unity. Finally, I switched to Unity and either enable debugging for the session or for all projects. At this point, there was a small green bug icon in the bottom right of Unity. After the scripts were compiled, I ran my game from Unity and the debugger was able to hit my breakpoints.</p><p>Here is the documentation page for this setup: <a href="https://code.visualstudio.com/docs/other/unity">Visual Studio Code and Unity</a>. I had already installed the Unity Debugger and C# extensions the first time I got it working. I also came across a <a href="https://www.youtube.com/watch?v=nHHIhsU4oO0&t=182s&ab_channel=Better-Coding" target="_blank">YouTube video of the VSCode for Unity setup</a>.</p><p>Searching around, I naturally found a tonne of suggestions to fix this problem. Here are some of the suggestions I found that did not help with my setup:</p><p></p><ul style="text-align: left;"><li>Reboot and restart Unity and VSCode</li><li>Import all assets. Assets > Reimport All</li><li>Close VS. Delete Assembly-CSharp.csproj and Assembly-CSharp-Editor.csproj (this file wasn't in my project) in your project folder. Open VS.</li><li>Editor Attaching enabled in External Tools. This option doesn't exist in my version.</li><li>Delete all .csproj and .sln files and regenerate them.</li></ul><p></p>Stephen Cawoodhttp://www.blogger.com/profile/07948009840630937442noreply@blogger.com0tag:blogger.com,1999:blog-10893214.post-42254406202455991892022-12-29T14:28:00.002-08:002022-12-31T16:16:07.090-08:00Debugging Pygame Zero Games in VSCode<p>As part of my ongoing effort to expose my kids to various science topics, I picked up a Python game development book for kids and I'm working through it with my youngest. As part of that process, I was just setting up Pygame and <a href="https://code.visualstudio.com/" target="_blank">Visual Studio Code (VSCode)</a> over the holidays.</p><p>The setup didn't take long, but I did run into an issue. When I ran our code from the terminal, the games worked fine. However, when we tried to debug from VSCode, the Pygame Zero game window wouldn't--Pygame opened on the taskbar but just closed again. I did find the solution, so I thought I'd same some other people some Googling and document it here. The book just references IDLE, so it doesn't get into running the example games in an IDE such as VSCode.</p><p>The book I chose is <a href="https://www.dk.com/ca/book/9781465473615-coding-games-in-python/" target="_blank">Coding Games in Python</a>*. It's specifically targeted at kids, so it's a colourful book filled with a basic introduction to coding and some simple game examples. I highly recommend it.</p><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihyiEyoe6kLvlyIk8_hbaOx6OngcyaKffJ3lNkl1dOr9AgZSTKbasdfnniI-TmDAIRo9WiEzZQaPXpm-XxtDf7G8XzavZY7f5mJF6uk-ahsFHE0qbt3dEQA4PWprQXS3QyQMJNVQlGHcxfbOsIjQSFjYrRauHQj9JOu5QY061-8js8AWGWvw/s1099/PythonGamesKidsBook_9781465473615_cover.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1099" data-original-width="920" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihyiEyoe6kLvlyIk8_hbaOx6OngcyaKffJ3lNkl1dOr9AgZSTKbasdfnniI-TmDAIRo9WiEzZQaPXpm-XxtDf7G8XzavZY7f5mJF6uk-ahsFHE0qbt3dEQA4PWprQXS3QyQMJNVQlGHcxfbOsIjQSFjYrRauHQj9JOu5QY061-8js8AWGWvw/s320/PythonGamesKidsBook_9781465473615_cover.png" width="268" /></a></div><p>Getting back to the issue, I could successfully set breakpoints, but as soon as they were done, Pygame would close, and the game window would never appear. The answer is on page 25 of the book (which I skipped because I already had the setup done :) and it's also in the <a href="https://pygame-zero.readthedocs.io/en/stable/ide-mode.html" target="_blank">Pygame Zero doc page about IDEs</a>. Here's the solution (paraphrased):</p><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><p><span style="font-family: verdana;">Pygame Zero includes a way of writing a full Python program that can be run using python. To set up VSCode for debugging, add this line at the top of your file:</span></p><p><span style="font-family: courier;">import pgzrun</span></p><p><span style="font-family: verdana;">Then add this line to the end of your file:</span></p><p><span style="font-family: courier;">pgzrun.go()</span></p></blockquote><p>* <a href="https://www.dk.com/ca/book/9781465473615-coding-games-in-python/" target="_blank">Coding Games in Python</a> was written by Carole Vorderman MBE, Craig Steele, Dr. Claire Quigley, Daniel McCafferty, and Dr. Martin Goodfellow.</p>Stephen Cawoodhttp://www.blogger.com/profile/07948009840630937442noreply@blogger.com0tag:blogger.com,1999:blog-10893214.post-35498022091108047762021-10-05T16:04:00.022-07:002023-01-24T14:40:49.935-08:0010 Things I Undervalued When I Started Playing MinecraftI started playing Minecraft a few years ago because I thought it would be an interesting game for my creative daughter. That turned out to be true (I even blogged about it - <a href="https://geeklit.blogspot.com/2019/06/minecraft-bonding-with-my-daughter.html" target="_blank">Minecraft bonding with my daughter</a>) but what I didn't anticipate was how much I would enjoy the game. Every Minecraft newbie runs into many pitfalls. However, which one they actually fall into will vary from player to player. These are the things that initially tripped me up or slowed my progress.<div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-obxaJ4weFtg/YYi_zltNGyI/AAAAAAAJxqY/SP24x9Mio_UqT7MxWtLOjYL02NYxCBCgQCLcBGAsYHQ/s1600/minecraft.jpeg" style="margin-left: 1em; margin-right: 1em;"><img alt="minecraft" border="0" data-original-height="675" data-original-width="1600" height="169" src="https://1.bp.blogspot.com/-obxaJ4weFtg/YYi_zltNGyI/AAAAAAAJxqY/SP24x9Mio_UqT7MxWtLOjYL02NYxCBCgQCLcBGAsYHQ/w400-h169/minecraft.jpeg" title="minecraft" width="400" /></a></div><br /><div>Update: I'm adding an eleventh item to the list because I just have to add curing zombie villagers as #3.<br /><h4 style="text-align: left;">11. Tridents</h4><div>At face value, you might assume swords are the best offensive weapon in the game. However, anyone who runs into a drowned with a trident might tell you otherwise. Tridents are certainly harder to come by--you'll probably have to find a few and fix them--but they do more damage. Also, they can be enchanted with Loyalty which turns them into a ranged weapon you can throw.</div><h4 style="text-align: left;">10. Ender Pearls</h4><div>Once you have a decent weapon and armour, you can get ender pearls relatively easily. Yes, you have to fight Enderman to do it, but you can win that fight. Ender pearls are incredibly powerful because they allow you to almost instantly teleport away from where you are. They are also one of the most effective defensive techniques in the game. If you get into trouble simply chuck a pearl and you'll be out of danger.</div><div><br /></div><div>Note: you can't throw an Ender pearl out of lava. I tried that once... but only once. 😉 </div><h4 style="text-align: left;">9. Charcoal </h4><div>Most people agree the very first thing you should do when you start a new world is to cut down a tree. This gives you wood for your first tools and you can get to work making stone tools, and then iron tools after that. However, the one functionality of wood that I underestimated is making charcoal. If you build a furnace quickly and cook logs, you get charcoal that you can use to make torches. Once you have torches, instead of hiding from mobs all night, you can use that time to start digging a mine and speed up your ability to find iron, lava, diamonds, and a lot of other useful resources.</div><h4 style="text-align: left;">8. Trading to get experience points</h4><div>It's surprisingly lucrative to trade with villagers. Yes, you get emeralds (or other items), but you also get experience points. If you breed villagers and put down the right work blocks for them, you can trade everything from rotten flesh to stone.</div><h4 style="text-align: left;">7. Spelunking</h4><div>At the beginning of the game (when you don't have durable tools), this is by far the best option for gathering iron and coal. Strip mining might be more thorough, but exploring caves doesn't require anywhere near the mining time and you can quickly examine a lot of blocks.</div><h4 style="text-align: left;">6. Lava as fuel</h4><div>Look it up, a bucket of lava can cook/burn more than any other fuel type. This allows you to save your coal for torches and trading with villagers.</div><h4 style="text-align: left;">5. Enchanting and combining books</h4><div>Books can even be combined to raise their enchantments. For example, if you combine two Fortune I books into one Fortune II book.</div><h4 style="text-align: left;">4. Automatic sugarcane farms</h4><div>Before I learned to build automatic sugarcane farms, leveling up villagers was a colossal pain in the chest plate. But now that I've tried them, I'm completely sold. I would never bother leveling up librarians without sugarcane farms again.</div><div><br /></div><div>Note: use CrunchBase to make sure you don't build your Redstone machines on chunk boundaries. If you build on a boundary, your machine will keep breaking--which is awful. However, it's easy to figure out where the boundaries are and avoid them.</div><h4>3. Curing Zombie Villagers</h4><h4 style="text-align: left;"><span style="font-weight: normal;">In my last world, I decided to try curing villagers instead of breeding them. Wow! What a difference. The discounted prices for trades are a game changer.</span></h4><div>I had cured villagers before, but only out of necessity when I didn't have two to breed. I always figured the golden apple requirement wasn't worth the gold--when I could be using that gold to build powered rails. But now gold is easier to acquire and it's definitely worth it. The difference for librarians alone is enough to justify it (I don't even really need an automatic sugarcane farm anymore), but when you add in all the trades for the armourers and the blacksmiths (and others), it's just such a resource saver.</div><h4 style="text-align: left;">2. Enchanting for durability</h4><div>I assume this is the biggest "no brainer" on this list, but I'd like to clarify exactly what I mean. Obviously, enchanting is great. However, what I didn't realize when I first started playing was just how key it is to get an enchanting table as fast as possible. The difference between using 'unbreaking' enchanted pick axes and vanilla ones can be the difference between constantly needing iron for tools, and using your iron ore for something else. When I start a new world, I dig down to around level 11 and start looking for diamonds. As soon as I find iron, I can make a bucket and take water down with me. Then, it's a simple matter of finding lava and using my water to create obsidian. Most likely, this is all easier than finding diamonds, but as soon as I do find them, I can make a diamond pickaxe (to mine the obsidian) and an enchanting table.</div><h4 style="text-align: left;">1. Fishing
</h4><div>A lot of people find fishing boring, but that wasn't why I initially undervalued its potential. At the beginning of a world, you get so much value from fishing. You can fish out experience points, food, saddles, enchanted books, enchanted fishing rods, and enchanted bows. The enchanted bows are probably the most important as you can get an infinity bow without an enchanting table. When you don't have much, an infinity bow is literally a game-changer.</div><div><br /></div><div>Note: remember that fishing at night in the rain is the most efficient. So build a platform above water so mobs can't get you and fish at night.</div><div><br /></div><div>That's my list. I hope you found it helpful.</div></div>Stephen Cawoodhttp://www.blogger.com/profile/07948009840630937442noreply@blogger.com0tag:blogger.com,1999:blog-10893214.post-52543266951874359222021-04-05T17:21:00.000-07:002021-04-05T17:21:30.504-07:00Reel BJJ Public Beta is live!<p>I'm super excited to have opened up the web application version of <a href="https://www.reelbjj.com/" target="_blank">Reel BJJ</a> for public Beta feedback. Reel BJJ is a video management application for athletes and coaches who want to get the most out of their Brazilian jiu-jitsu videos. (Try it now--there's a <a href="https://www.reelbjj.com/" target="_blank">free trial</a>.)</p><div class="separator" style="clear: both; text-align: center;"><a href="https://www.reelbjj.com/" style="margin-left: 1em; margin-right: 1em;" target="_blank"><img alt="Reel BJJ logo" border="0" data-original-height="256" data-original-width="256" height="108" src="https://1.bp.blogspot.com/-F6hYC2aM5D0/YERzb9EZdWI/AAAAAAAJLMk/cYV8EhHZyK0aF6eq61hJMOCzjlHr4DBPgCLcBGAsYHQ/w108-h108/reelBJJ_logo.png" style="border-radius: 50%;" title="Reel BJJ logo" width="108" /></a></div><br /><p>Why create a <a href="https://www.reelbjj.com/" target="_blank">jiu-jitsu video app</a>? The answer is simple. There is an amazing library of jiu-jitsu content available from a number of sites such as <a href="https://bjjfanatics.com/" target="_blank">BJJ Fanatics</a>, <a href="https://www.grapplearts.com/" target="_blank">GrappleArts</a>, <a href="https://grapplersguide.com/" target="_blank">Grapplers Guide</a>, <a href="https://bjjlibrary.com/" target="_blank">BJJ Library</a>, and <a href="https://www.gracieuniversity.com/" target="_blank">Gracie University</a>--the list goes on. In fact, in a recent <a href="https://www.youtube.com/watch?v=eJ6J9Ch--AI" target="_blank">interview with Bernardo Faria on BJJ Fanatics, Mikey Muscimeci</a> made exactly this point. The content available to people wanting to learn techniques is seemingly endless. However, the way video is used for learning jiu-jitsu hasn't changed--most people just watch an instructional through and hope they remember the best parts. The mission of Reel BJJ is to change the way people think about learning jiu-jitsu via video.</p><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-XYp_4cHwJlk/YEB8Bb3x8ZI/AAAAAAAJLI0/Ch0NNpIAPg46zXVyBtcXy42u_Iq8N-sJgCPcBGAYYCw/s2048/Reel%2BBJJ%2BSample%2BReel%2BScreenshot.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1052" data-original-width="2048" height="205" src="https://1.bp.blogspot.com/-XYp_4cHwJlk/YEB8Bb3x8ZI/AAAAAAAJLI0/Ch0NNpIAPg46zXVyBtcXy42u_Iq8N-sJgCPcBGAYYCw/w400-h205/Reel%2BBJJ%2BSample%2BReel%2BScreenshot.png" width="400" /></a></div><br /><div class="separator" style="clear: both; text-align: center;">- Reel BJJ allows users to create Reels without video editing</div><p>In short, Reel BJJ can help everyone (athletes, instructors, coaches, or hobbyists) get the most value possible from video content. The first main feature of the <a href="https://www.reelbjj.com/" target="_blank">Reel BJJ Beta release</a> is called Reels (and before anyone gets upset with me, I created Reels a while back, so yes, it was before Instagram stole my thunder). Reels allow anyone to quickly (and easily) create highlights from their jiu-jitsu videos and then just watch the parts of the videos they want to review. (You can see how Reels work in this short <a href="https://www.youtube.com/watch?v=rwTwhimd9s8" target="_blank">walkthrough video of the Reel BJJ Beta</a>.)</p><p>Some of the most common scenarios for Reels would include:</p><p></p><ul style="text-align: left;"><li>Techniques you'd like to learn</li><li>Techniques you'd like to try before class</li><li>Review footage of your sparring rounds</li><li>Review footage of your competition matches</li><li>Researching opponents for upcoming competitions</li><li>Creating a game plan of techniques</li></ul><p></p><p><span>The key, of course, is that Reel BJJ users can do this without any software install or video editing. Users can add videos from YouTube, Vimeo, or even upload their own videos, and then create highlights. Naturally, you'll want to add notes to your highlights, so Reel BJJ has a cool notes features that was just shipped.</span></p><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-knZeJH0drbM/YERzHXArj4I/AAAAAAAJLMc/6OimQKLJYgQi2mQM6JeXlaU7pcHv_t7LQCLcBGAsYHQ/s2048/ReelBJJNotes.png" style="margin-left: 1em; margin-right: 1em; text-align: center;"><img alt="Reel BJJ Beta Notes" border="0" data-original-height="1215" data-original-width="2048" height="238" src="https://1.bp.blogspot.com/-knZeJH0drbM/YERzHXArj4I/AAAAAAAJLMc/6OimQKLJYgQi2mQM6JeXlaU7pcHv_t7LQCLcBGAsYHQ/w400-h238/ReelBJJNotes.png" title="Reel BJJ Beta Notes" width="400" /></a></div><p></p><p>It's important to note that Reel BJJ does not download videos from streaming services, or violate their terms of service in any way. The videos remain at the source and aren't altered in any way.</p>I'm excited to see what will happen. The Beta feedback so far has been fantastic. For example, here's some feedback from <a href="https://www.instagram.com/thomaslisboajj/" target="_blank">Thomas Lisboa</a>.<div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://www.instagram.com/thomaslisboajj/" style="margin-left: 1em; margin-right: 1em;" target="_blank"><img border="0" data-original-height="225" data-original-width="225" src="https://1.bp.blogspot.com/-ULxGLb5Kkyk/YER0cgbPnVI/AAAAAAAJLMs/1qdGUAxrS3QSyGXzhlbbIn8IceHm19fogCLcBGAsYHQ/s0/Thomas_Lisboa.jpeg" style="border-radius: 50%;" /></a></div><p style="text-align: center;">"It's unbelievable how jiu-jitsu is constantly evolving. That's why I was so excited when I discovered Reel BJJ. I can select the parts I liked most in a course and review them. For those who like to study jiu-jitsu, regardless of their level and goals, Reel BJJ is the evolution of jiu-jitsu study." - <b><a href="https://www.instagram.com/thomaslisboajj/" target="_blank">Thomas Lisboa</a> (Head Coach of <a href="https://www.alliancebjjteam.com/" target="_blank">Alliance BJJ Vancouver</a>)</b></p><p><br /></p></div>Stephen Cawoodhttp://www.blogger.com/profile/07948009840630937442noreply@blogger.com0tag:blogger.com,1999:blog-10893214.post-44997229506436883672020-11-11T23:28:00.000-08:002021-01-11T23:41:54.822-08:00Group Policy Prevents Microsoft Apps Working on Windows 10<p>I recently ran into an odd problem on my home Windows 10 PC. Even though the machine is my home PC and isn't part of a domain, I started getting a Group Policy error any time I tried to use a program that requires a Microsoft account. This is the error:</p><p>"This program is blocked by group policy. For more information contact your system administrator. 0x800704ec"</p><p>This error can affect any app or program that uses your Microsoft account. This includes things like the Microsoft Store, the Xbox app, the Feeback app, and even Minecraft.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-FJ56XBrcNBo/X_1QTXhHViI/AAAAAAAJJgc/WUfSZGQaG0ULV1q1up7J8IXcBZUsQeTpQCLcBGAsYHQ/s761/groupolicy1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img alt="Group Policy Windows 10 issue geeklit.com" border="0" data-original-height="699" data-original-width="761" height="368" src="https://1.bp.blogspot.com/-FJ56XBrcNBo/X_1QTXhHViI/AAAAAAAJJgc/WUfSZGQaG0ULV1q1up7J8IXcBZUsQeTpQCLcBGAsYHQ/w400-h368/groupolicy1.png" width="400" /></a></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-ZwUWC9l8Kc0/X_1QTSxYaZI/AAAAAAAJJgY/2Za7osQioMUP23u8Lq8gQMV-S13voLrAACLcBGAsYHQ/s560/grouppolicy2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img alt="Group Policy Windows 10 issue geeklit.com" border="0" data-original-height="420" data-original-width="560" height="300" src="https://1.bp.blogspot.com/-ZwUWC9l8Kc0/X_1QTSxYaZI/AAAAAAAJJgY/2Za7osQioMUP23u8Lq8gQMV-S13voLrAACLcBGAsYHQ/w400-h300/grouppolicy2.png" width="400" /></a></div><br /><p>This error happens when your Microsoft account has been linked to a work or school account that does have a group policy that's preventing the app from running properly. To fix the issue, disassociate the problematic account from your home Microsoft account.</p><p>To remove the account, go to Settings > Accounts > Access Work or School. Find any reference to a work or school account associated with your account and choose disconnect. Remember, this is all assuming that you don't mind not having that account associated with your home account.</p>Stephen Cawoodhttp://www.blogger.com/profile/07948009840630937442noreply@blogger.com0tag:blogger.com,1999:blog-10893214.post-67178441872123143312020-10-27T12:10:00.000-07:002020-10-27T12:10:17.285-07:00Cypress.io testing with Auth0 and Angular 10<p>I read the documentation on both the <a href="https://auth0.com/" target="_blank">Auth0</a> site and the <a href="http://Cypress.io" target="_blank">Cypress.io</a> site about using the two technologies together with the <a href="https://angular.io/" target="_blank">Angular framework</a>, but I simply couldn't get it to work.</p><p>For example, the tutorial on the <a href="https://auth0.com/blog/complete-guide-to-angular-user-authentication/" target="_blank">Auth0 Blog called The Complete Guide to Angular User Authentication with Auth0</a> got me most of the way there and the same was true for the <a href="https://gist.github.com/kevinold/31dbbebd1cb75f311d798f5bc81574a4" target="_blank">Cypress.io "real-world app" code and documentation on GitHub</a> (which is written for React BTW).</p><p>These two resources (plus some community posts) allowed me to set up all the underpinnings of a working test system but didn't resolve all my issues. In the past, my Angular project exclusively used Google Authentication, so my Cypress tests required me manually login first, and then run all the tests that needed an authenticated user. It wasn't perfect, but it was workable. However, his manual login option did not work with Auth0 (and Angular 10). Google Authentication would not allow the login window to open in an iFrame when running in the context of a Cypress test (i.e., Cypress was automating/controlling the browser).</p><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-GE_ig_rM398/X5hwFYeKPAI/AAAAAAAJFbQ/9H2i6N3W_pwWv6lRjzDgcjaHodxMDiALACLcBGAsYHQ/s1942/Screen%2BShot%2B2020-10-27%2Bat%2B12.07.41%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1036" data-original-width="1942" height="214" src="https://1.bp.blogspot.com/-GE_ig_rM398/X5hwFYeKPAI/AAAAAAAJFbQ/9H2i6N3W_pwWv6lRjzDgcjaHodxMDiALACLcBGAsYHQ/w400-h214/Screen%2BShot%2B2020-10-27%2Bat%2B12.07.41%2BPM.png" width="400" /></a></div><div class="separator" style="clear: both; text-align: center;">- Auth0 authenticated tests running successfully in Cypress.io and Angular 10</div><br /><p><br /></p><p>This is what I ultimately did to get it working:</p><p>1. Switch my Node.js API from Google tokens to Auth0 authentication tokens by following the <a href="https://auth0.com/docs/quickstart/backend/nodejs" target="_blank">Auth0 quickstart for Node.js (Express) backends</a>.</p><p>2. Set up a test SPA Application in Auth0 with lower security than my production app. This allowed me to enable username/password logins and turn on the Password Grant Types (under Application > Advanced Settings).</p><p>3. Follow the Cypress real-world app section called "<a href="https://gist.github.com/kevinold/31dbbebd1cb75f311d798f5bc81574a4" target="_blank">Cypress Setup for Testing Auth0</a>) to add a Login function to my Cypress.io setup.</p><p>Under Cypress > support > commands.ts, I now have the code below. You can also use plain JavaScript of course. The configuration for the variables is found under src > cypress.env.json.</p><p><span style="font-family: courier;">/// <reference types="cypress" /></span></p><p><span style="font-family: courier;">/// <reference types="jwt-decode" /></span></p><p><span style="font-family: courier;">import jwt_decode from 'jwt-decode';</span></p><p><br /></p><p><span style="font-family: courier;">Cypress.Commands.add('login', (overrides = {}) => {</span></p><p><span style="font-family: courier;"> const username = Cypress.env('auth0_username');</span></p><p><span style="font-family: courier;"> cy.log(`Logging in as ${username}`);</span></p><p><span style="font-family: courier;"> cy.request({</span></p><p><span style="font-family: courier;"> method: "POST",</span></p><p><span style="font-family: courier;"> url: Cypress.env('auth0_url'),</span></p><p><span style="font-family: courier;"> body: {</span></p><p><span style="font-family: courier;"> grant_type: 'password',</span></p><p><span style="font-family: courier;"> username: Cypress.env('auth0_username'),</span></p><p><span style="font-family: courier;"> password: Cypress.env('auth0_password'),</span></p><p><span style="font-family: courier;"> audience: Cypress.env('auth0_audience'),</span></p><p><span style="font-family: courier;"> scope: Cypress.env('auth0_scope'),</span></p><p><span style="font-family: courier;"> client_id: Cypress.env('auth0_client_id'),</span></p><p><span style="font-family: courier;"> client_secret: Cypress.env('auth0_client_secret'), </span></p><p><span style="font-family: courier;"> },</span></p><p><span style="font-family: courier;"> }).then(({ body }) => {</span></p><p><span style="font-family: courier;"> const claims: any = jwt_decode(body.id_token);</span></p><p><span style="font-family: courier;"> const { nickname, name, picture, updated_at, email, email_verified, sub, exp } = claims;</span></p><p><span style="font-family: courier;"><br /></span></p><p><span style="font-family: courier;"> const item = {</span></p><p><span style="font-family: courier;"> body: {</span></p><p><span style="font-family: courier;"> ...body,</span></p><p><span style="font-family: courier;"> decodedToken: {</span></p><p><span style="font-family: courier;"> claims,</span></p><p><span style="font-family: courier;"> user: {</span></p><p><span style="font-family: courier;"> nickname,</span></p><p><span style="font-family: courier;"> name,</span></p><p><span style="font-family: courier;"> picture,</span></p><p><span style="font-family: courier;"> updated_at,</span></p><p><span style="font-family: courier;"> email,</span></p><p><span style="font-family: courier;"> email_verified,</span></p><p><span style="font-family: courier;"> sub,</span></p><p><span style="font-family: courier;"> },</span></p><p><span style="font-family: courier;"> audience: '',</span></p><p><span style="font-family: courier;"> client_id: '',</span></p><p><span style="font-family: courier;"> },</span></p><p><span style="font-family: courier;"> },</span></p><p><span style="font-family: courier;"> expiresAt: exp,</span></p><p><span style="font-family: courier;"> };</span></p><p><span style="font-family: courier;"><br /></span></p><p><span style="font-family: courier;"> window.localStorage.setItem('auth0Cypress', JSON.stringify(item));</span></p><p><span style="font-family: courier;"> return body;</span></p><p><span style="font-family: courier;"> });</span></p><p><span style="font-family: courier;">});</span></p><p><span style="font-family: courier;"><br /></span></p><p><span style="font-family: courier;">let LOCAL_STORAGE_MEMORY = {};</span></p><p><span style="font-family: courier;">Cypress.Commands.add("saveLocalStorageCache", () => {</span></p><p><span style="font-family: courier;"> Object.keys(localStorage).forEach(key => {</span></p><p><span style="font-family: courier;"> LOCAL_STORAGE_MEMORY[key] = localStorage[key];</span></p><p><span style="font-family: courier;"> });</span></p><p><span style="font-family: courier;">});</span></p><p><span style="font-family: courier;"><br /></span></p><p><span style="font-family: courier;">Cypress.Commands.add("restoreLocalStorageCache", () => {</span></p><p><span style="font-family: courier;"> Object.keys(LOCAL_STORAGE_MEMORY).forEach(key => {</span></p><p><span style="font-family: courier;"> localStorage.setItem(key, LOCAL_STORAGE_MEMORY[key]);</span></p><p><span style="font-family: courier;"> });</span></p><p><span style="font-family: courier;">});</span></p><p><br /></p><p>4. With this command added to Cypress.io, I can now login programmatically to Auth0 using the following code in my first test. You'll note that the authenticated user details are being written in local storage (but only during testing), and the Auth0 authenticated token is being stored as a cookie--this is the token that I use with my backend API.</p><p><br /></p><p><span style="font-family: courier;">describe('Login', () => {</span></p><p><span style="font-family: courier;"> beforeEach(() => {</span></p><p><span style="font-family: courier;"> cy.restoreLocalStorageCache();</span></p><p><span style="font-family: courier;"> });</span></p><p><span style="font-family: courier;"> </span></p><p><span style="font-family: courier;"> it('Should successfully login', () => {</span></p><p><span style="font-family: courier;"> cy.login2()</span></p><p><span style="font-family: courier;"> .then((resp) => {</span></p><p><span style="font-family: courier;"> return resp;</span></p><p><span style="font-family: courier;"> })</span></p><p><span style="font-family: courier;"> .then((body) => {</span></p><p><span style="font-family: courier;"> const {access_token, expires_in, id_token} = body;</span></p><p><span style="font-family: courier;"> const auth0State = {</span></p><p><span style="font-family: courier;"> nonce: '',</span></p><p><span style="font-family: courier;"> state: 'some-random-state'</span></p><p><span style="font-family: courier;"> };</span></p><p><span style="font-family: courier;"><br /></span></p><p><span style="font-family: courier;"> // write access token to user-token cookie</span></p><p><span style="font-family: courier;"> cy.setCookie('user-token', access_token);</span></p><p><span style="font-family: courier;"><br /></span></p><p><span style="font-family: courier;"> const callbackUrl = `/callback#access_token=${access_token}&scope=openid&id_token=${id_token}&expires_in=${expires_in}&token_type=Bearer&state=${auth0State.state}`;</span></p><p><span style="font-family: courier;"> cy.visit(callbackUrl, {</span></p><p><span style="font-family: courier;"> onBeforeLoad(win) {</span></p><p><span style="font-family: courier;"> win.document.cookie = 'com.auth0.auth.some-random-state=' + JSON.stringify(auth0State);</span></p><p><span style="font-family: courier;"> }</span></p><p><span style="font-family: courier;"> });</span></p><p><span style="font-family: courier;"> })</span></p><p><span style="font-family: courier;"> });</span></p><p><span style="font-family: courier;"> afterEach(() => {</span></p><p><span style="font-family: courier;"> cy.saveLocalStorageCache();</span></p><p><span style="font-family: courier;"> });</span></p><p><span style="font-family: courier;">});</span></p><p><br /></p><p>6. This all seemed to work great, however, Auth0 still would not recognize that the user is authenticated. The Auth0 client would always return false for isAuthenticated. To get around this issue, I had to hack my AuthGuard.</p><p>This is what I had before adding Cypress.io:</p><p><span style="font-family: courier;">return this.auth.isAuthenticated$.pipe(</span></p><p><span style="font-family: courier;"> tap(loggedIn => {</span></p><p><span style="font-family: courier;"> if (!loggedIn) {</span></p><p><span style="font-family: courier;"> this.auth.login(state.url);</span></p><p><span style="font-family: courier;"> }</span></p><p><span style="font-family: courier;"> })</span></p><p><span style="font-family: courier;">);</span></p><p><br /></p><p><span style="font-family: inherit;">To get around the issue, I simple added another option. If the code is being run by Cypress, I check for the stored user credential and token. I even added a check that it's the right authenticated user, but that's really not needed.</span></p><p><span style="font-family: inherit;"><br /></span></p><p> <span style="font-family: courier;"> // @ts-ignore</span></p><p><span style="font-family: courier;"> if (window.Cypress) {</span></p><p><span style="font-family: courier;"> const auth0credentials = JSON.parse(localStorage.getItem("auth0Cypress")!);</span></p><p><span style="font-family: courier;"> const user = auth0credentials.body.decodedToken.user;</span></p><p><span style="font-family: courier;"> const access_token = auth0credentials.body.access_token;</span></p><p><span style="font-family: courier;"><br /></span></p><p><span style="font-family: courier;"> if(user.name === 'youtestuser@yourdomain.com' && access_token) {</span></p><p><span style="font-family: courier;"> return true;</span></p><p><span style="font-family: courier;"> } else {</span></p><p><span style="font-family: courier;"> return this.auth.isAuthenticated$.pipe(</span></p><p><span style="font-family: courier;"> tap(loggedIn => {</span></p><p><span style="font-family: courier;"> if (!loggedIn) {</span></p><p><span style="font-family: courier;"> } else {</span></p><p><span style="font-family: courier;"> this.auth.login(state.url);</span></p><p><span style="font-family: courier;"> }</span></p><p><span style="font-family: courier;"> })</span></p><p><span style="font-family: courier;"> );</span></p><p><span style="font-family: courier;"> };</span></p><p><span style="font-family: courier;"> } else {</span></p><p><span style="font-family: courier;"> return this.auth.isAuthenticated$.pipe(</span></p><p><span style="font-family: courier;"> tap(loggedIn => {</span></p><p><span style="font-family: courier;"> if (!loggedIn) {</span></p><p><span style="font-family: courier;"> this.auth.login(state.url);</span></p><p><span style="font-family: courier;"> }</span></p><p><span style="font-family: courier;"> })</span></p><p><span style="font-family: courier;"> );</span></p><p><span style="font-family: courier;"> }</span></p><p><br /></p><p>Well, I think that's everything. I hope there is a better answer coming as this hack around the AuthGuard is not a prefect solution, but it does let me move forward and that's promising.</p><p><br /></p>Stephen Cawoodhttp://www.blogger.com/profile/07948009840630937442noreply@blogger.com0tag:blogger.com,1999:blog-10893214.post-88725051742403337622020-05-27T21:13:00.000-07:002020-06-13T12:29:25.357-07:00Debugging TypeScript Phaser Apps Server and Client-side Using VS CodeI’m using the Phaser game development platform to teach my kids some video game design and programming concepts (I previously used <a href="https://geeklit.blogspot.com/2017/01/building-video-games-with-my-daughter.html" target="_blank">LEGO to draw my daughter into building game sprites</a>). As a new <a href="https://phaser.io/" target="_blank">Phaser</a> user, I'm finding there isn't enough writing out there about using the <a href="https://phaser.io/" target="_blank">Phaser 3 HTML5 game development platform</a> with TypeScript, so I feel I should add my 0.02c. My first contribution is about debugging with breakpoints in Phaser and Visual Studio Code (VSCode).<br />
<br />
<blockquote class="tr_bq">
Note: You can read more about setting up <a href="https://code.visualstudio.com/docs/typescript/typescript-debugging" target="_blank">VSCode for TypeScript debugging</a> on the Microsoft site, but one tip is to remember you'll need to make sure you have <span style="font-family: Courier New, Courier, monospace;">"sourceMap": true</span> in your tsconfig.json file.</blockquote>
<br />
Most likely, if you're using Phaser for a multi-player game, you're probably using Node.js and Express to fire up a server for the Phaser client-side code. I found this complicated debugging with breakpoints in VS Code, and I haven't been able to get a single configuration to hit breakpoints in both the server and client-side code. This makes sense, but I still thought it would be handled more easily.<br />
<br />
If I launch a debugger for the server, any breakpoints I've set in my client-side code show "Breakpoint set but not yet bound" or the more direct error, "Breakpoint ignored because generated code not found (source map problem?)" My workaround is to launch the debugger depending on which breakpoints I need.<br />
<h3>
Server-side debugging</h3>
To debug the server, use VSCode attach to process and choose the dist/server.js process (server.js is the transpiled version of my server.ts file that starts the Node.js server).<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-ruh2CD9GUaY/Xs38hJfxvEI/AAAAAAAI-Vs/iC3wPdCnmHgbZdNc840nnaJeaJ-trcgXACLcBGAsYHQ/s1600/vscode_breakpoint_geeklit_2020-05-26.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img alt="VS Code server-side breakpoint Geeklit Blog" border="0" data-original-height="763" data-original-width="993" height="245" src="https://1.bp.blogspot.com/-ruh2CD9GUaY/Xs38hJfxvEI/AAAAAAAI-Vs/iC3wPdCnmHgbZdNc840nnaJeaJ-trcgXACLcBGAsYHQ/s320/vscode_breakpoint_geeklit_2020-05-26.png" title="VS Code server-side breakpoint Geeklit Blog" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<h3>
Client-side debugging</h3>
To debug the client (uses <a href="https://code.visualstudio.com/blogs/2016/02/23/introducing-chrome-debugger-for-vs-code" target="_blank">Visual Studio Code Chrome debugger extension</a>), I use VSCode to launch the debugger with the Launch client-side task.<br />
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Here is the entire launch.json file that supports both client and server-side debugging with breakpoints: </div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: "courier new" , "courier" , monospace;">{</span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: "courier new" , "courier" , monospace;"> // Use IntelliSense to learn about possible attributes.</span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: "courier new" , "courier" , monospace;"> // Hover to view descriptions of existing attributes.</span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: "courier new" , "courier" , monospace;"> // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387</span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: "courier new" , "courier" , monospace;"> "version": "0.2.0",</span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: "courier new" , "courier" , monospace;"> "configurations": [</span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: "courier new" , "courier" , monospace;"> {</span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: "courier new" , "courier" , monospace;"> "name": "Launch client-side: localhost:8080",</span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: "courier new" , "courier" , monospace;"> "type": "chrome",</span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: "courier new" , "courier" , monospace;"> "request": "launch",</span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: "courier new" , "courier" , monospace;"> "url": "http://localhost:8080/index.html",</span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: "courier new" , "courier" , monospace;"> "webRoot": "${workspaceFolder}/dist/client"</span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: "courier new" , "courier" , monospace;"> },</span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: "courier new" , "courier" , monospace;"> {</span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: "courier new" , "courier" , monospace;"> "name": "Attach to Process dist/server.js</span><span style="font-family: "courier new" , "courier" , monospace;">",</span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: "courier new" , "courier" , monospace;"> "type": "node",</span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: "courier new" , "courier" , monospace;"> "request": "attach",</span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: "courier new" , "courier" , monospace;"> "processId": "${command:PickProcess}",</span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: "courier new" , "courier" , monospace;"> "port": 8080</span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: "courier new" , "courier" , monospace;"> }</span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: "courier new" , "courier" , monospace;"> ]</span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: "courier new" , "courier" , monospace;">}</span></div>
<br />Stephen Cawoodhttp://www.blogger.com/profile/07948009840630937442noreply@blogger.com1tag:blogger.com,1999:blog-10893214.post-40253324773830552482020-04-21T23:01:00.000-07:002020-06-13T12:32:40.600-07:00Running NPM in Windows Terminal Ubuntu profileI got some nasty errors when I tried to use NPM and Node.js in the new <a href="https://docs.microsoft.com/en-us/windows/terminal/" target="_blank">Windows Terminal</a> for Windows 10 (which BTW has a cool split-pane feature that might just make it my new goto terminal).<br />
<br />
The errors looked like this:<br />
<br />
<pre style="background-color: #f6f8fa; border-radius: 3px; box-sizing: border-box; color: #24292e; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace; font-size: 11.9px; line-height: 1.45; margin-bottom: 16px; overflow-wrap: normal; overflow: auto; padding: 16px;"><code style="background: initial; border-radius: 3px; border: 0px; box-sizing: border-box; display: inline; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace; font-size: 11.9px; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;"> not foundram Files/nodejs/npm: 3: /mnt/c/Program Files/nodejs/npm:
: not foundram Files/nodejs/npm: 5: /mnt/c/Program Files/nodejs/npm:
/mnt/c/Program Files/nodejs/npm: 6: /mnt/c/Program Files/nodejs/npm: Syntax error: word unexpected (expecting "in")</code></pre>
<br />
Here's the fix<br />
<br />
<pre style="background-color: #f6f8fa; border-radius: 3px; box-sizing: border-box; color: #6a737d; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace; font-size: 11.9px; line-height: 1.45; overflow-wrap: normal; overflow: auto; padding: 16px;"><code style="background: initial; border-radius: 3px; border: 0px; box-sizing: border-box; display: inline; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace; font-size: 11.9px; line-height: inherit; margin: 0px; overflow-wrap: normal; overflow: visible; padding: 0px; word-break: normal;">$ sudo apt-get update
$ sudo apt-get install -y nodejs
$ sudo apt-get install build-essential
$ sudo apt get install npm
**CLOSE AND REOPEN TERMINAL**</code></pre>
<br />
I found it here: <a href="https://github.com/microsoft/WSL/issues/1512">https://github.com/microsoft/WSL/issues/1512</a><br />
<br />
BTW -- example split-pane keys: Alt+Shift+-, Alt+Shift+= will split horizontally and vertically.<br />
<br />Stephen Cawoodhttp://www.blogger.com/profile/07948009840630937442noreply@blogger.com0tag:blogger.com,1999:blog-10893214.post-75173315640468840512019-06-16T16:22:00.000-07:002019-09-18T16:29:59.474-07:00Minecraft Bonding With My Daughter<br />
I hadn't played much <a href="https://en.wikipedia.org/wiki/Minecraft" target="_blank">Minecraft</a> since a co-worker showed me an original beta version, but I knew enough about the best selling PC game to suggest that my creative daughter give it a try. I thought it would be a great outlet for her crafty passion and also a nice way to get some more exposure to computers. At first, we tried Creative mode and having just watched The Swiss Family Robinson, we jumped into building a treehouse. We spent a reasonable amount of time working on that world and then one (or both?) of us suggested we try Survival mode.<br />
<br />
Survival mode is a real-time strategy game and is therefore highly addictive. Before we knew it, we were using any time we could to build up our world and it got quite intense. The first time my daughter fell into lava, she was distraught because she had lost everything she was carrying: tools, weapons, supplies, etc. I started to regret ever leaving Creative mode, but things calmed down and we're now at a reasonable place where it's just one of the things we do and it's not such a big deal if someone goes swimming in lava.<br />
<br />
Here's a screenshot of the first survival house we built that wasn't just a shelter from monsters.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-NTQzPUFWiqg/XYKzA9DSuKI/AAAAAAAIsZk/O-Knl4UgEgM0-T4I-8pchdAGb3WUMXJSACLcBGAsYHQ/s1600/2019-07-21_12-26-27_AM-s1m5uxoh.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img alt="Minecraft house cawood blog geeklit.com" border="0" data-original-height="900" data-original-width="1600" height="360" src="https://1.bp.blogspot.com/-NTQzPUFWiqg/XYKzA9DSuKI/AAAAAAAIsZk/O-Knl4UgEgM0-T4I-8pchdAGb3WUMXJSACLcBGAsYHQ/s640/2019-07-21_12-26-27_AM-s1m5uxoh.png" title="Minecraft house cawood blog geeklit.com" width="640" /></a></div>
<br />
<br />
The best part is that she got interested in Redstone engineering. For anyone who doesn't know, Redstone is the Minecraft way to create circuits that can power machines, traps, and other things in Minecraft. My wife suggested a lighthouse would be cool, so we build a functioning lighthouse together using Redstone. She even has her own Creative mode test world where she tries out Redstone inventions before she uses them in her other worlds. It's super cool.<br />
<br />
I highly recommend this sort of cooperative play with your kids--just watch those screen time limits. We've thoroughly enjoyed the experience and my daughter has even started doing a Minecraft after school program.<br />
<br />Stephen Cawoodhttp://www.blogger.com/profile/07948009840630937442noreply@blogger.com0tag:blogger.com,1999:blog-10893214.post-2007864923504947332018-04-08T13:59:00.000-07:002019-02-08T14:09:39.030-08:00Deploying Python app to Google Cloud Platform (GCP) fails for numpy==1.9.3I'm not sure what caused this issue. I've had this <a href="https://cloud.google.com/appengine/docs/standard/python3/quickstart" target="_blank">GCP Python</a> app running for many months, but today the deploy command failed with this error.<br />
<br />
<style type="text/css">
p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; background-color: #808080; background-color: rgba(128, 128, 128, 0.5)}
span.s1 {font-variant-ligatures: no-common-ligatures}
</style>
<br />
<div class="p1">
<span class="s1">$ gcloud app deploy ~/appFolder/app.yaml ~/appFolder/cron.yaml --verbosity=error</span></div>
<br />
<div class="p1">
<span class="s1">Step #1: </span><span class="s2"><span class="Apple-converted-space"> </span>Could not find a version that satisfies the requirement numpy==1.9.3 (from versions: 1.14.5, 1.14.6, 1.15.0rc2, 1.15.0, 1.15.1, 1.15.2, 1.15.3, 1.15.4, 1.16.0rc1, 1.16.0rc2, 1.16.0, 1.16.1)</span></div>
<div class="p1">
<span class="s2">Step #1: No matching distribution found for numpy==1.9.3</span></div>
<div class="p1">
<span class="s2">Step #1: You are using pip version 10.0.1, however version 19.0.1 is available.</span></div>
<div class="p1">
<span class="s2">Step #1: You should consider upgrading via the 'pip install --upgrade pip' command.</span></div>
<div class="p1">
<span class="s2">Step #1: The command '/bin/sh -c pip install -r requirements.txt' returned a non-zero code: 1</span></div>
<div class="p1">
<span class="s2">Finished Step #1</span></div>
<div class="p1">
<span class="s2">ERROR</span></div>
<div class="p1">
<span class="s2">ERROR: build step 1 "gcr.io/cloud-builders/docker@sha256:1f0bba269252a1b2cde650368ddc970afe80207986a88b8a0011e94b8xxxxxx" failed: exit status 1</span></div>
<div class="p1">
<span class="s2">Step #1:<span class="Apple-converted-space"> </span></span></div>
<div class="p2">
<span class="s2">-------------------------------------------------------------------------------------------------------------</span></div>
<div class="p3">
<span class="s2"></span><br /></div>
<style type="text/css"> p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #aa0003; background-color: #808080; background-color: rgba(128, 128, 128, 0.5)} p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; background-color: #808080; background-color: rgba(128, 128, 128, 0.5)} p.p3 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; background-color: #808080; background-color: rgba(128, 128, 128, 0.5); min-height: 13.0px} span.s1 {font-variant-ligatures: no-common-ligatures; color: #000000} span.s2 {font-variant-ligatures: no-common-ligatures} span.s3 {font-variant-ligatures: no-common-ligatures; color: #770002} </style>
<br />
<div class="p2">
<span class="s3"><b>ERROR:</b></span><span class="s2"> (gcloud.app.deploy) Cloud build failed.<span class="Apple-converted-space"> </span></span></div>
<br />
To resolve the issue, I changed this line in requirements.txt from:<br />
<br />
<div style="background-color: #1e1e1e; color: #d4d4d4; font-family: Menlo, Monaco, "Courier New", monospace; font-size: 12px; line-height: 18px; white-space: pre;">
<span style="color: #4ec9b0;">pandas</span>==<span style="color: #b5cea8;">0.22.0</span></div>
<br class="Apple-interchange-newline" />to:<br />
<br />
<div style="background-color: #1e1e1e; color: #d4d4d4; font-family: Menlo, Monaco, "Courier New", monospace; font-size: 12px; line-height: 18px; white-space: pre;">
<div>
<span style="color: #4ec9b0;">pandas</span>; python_version >= <span style="color: #ce9178;">'3.5'</span></div>
<div>
<span style="color: #4ec9b0;">pandas</span><<span style="color: #b5cea8;">0.21</span>; python_version == <span style="color: #ce9178;">'3.4'</span></div>
</div>
<br />
Then I ran:<br />
<br />
<style type="text/css">
p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; background-color: #808080; background-color: rgba(128, 128, 128, 0.5)}
span.s1 {font-variant-ligatures: no-common-ligatures}
</style>
<br />
<div class="p1">
<span class="s1">$ pip install<span class="Apple-converted-space"> </span>-r requirements.txt</span></div>
<br />
I found this solution in a comment on this GitHub issue: <a href="https://github.com/pandas-dev/pandas/issues/20697">https://github.com/pandas-dev/pandas/issues/20697</a><br />
<br />Stephen Cawoodhttp://www.blogger.com/profile/07948009840630937442noreply@blogger.com0tag:blogger.com,1999:blog-10893214.post-91255811400744396702018-03-31T22:13:00.000-07:002018-12-28T22:18:41.437-08:00Using PS3 wireless controllers with SUMOSYSI got some inexpensive unofficial PS3 wireless controllers for my SUMOSYS retro game console and the standard setup instructions didn't work. Here's how I got them working with SUMOSYS 700.<br />
<br />
1. Connect one working default controller and one new wireless controller (with the USB cable).<br />
(In my case the wireless controller was constantly vibrating--don't worry about this.)<br />
2. Go to the menu, choose controller setup and select the buttons. The controller should now work with the cable attached (but possibly still vibrating).<br />
3. Detach the cable from the new controller and go to the controller menu again with the controller flashing for Bluetooth connection. I didn't have to do anything else here--it just found it.<br />
4. The wireless controller was now working, so I unplugged the old wired controller, went to the controller menu and assigned the new controller to P1 (player 1 in games).<br />
5. Used the menu to shut down. This saves the first controller.<br />
Repeat the process for the other controllers, but you can skip connecting the wired controller since you have a working wireless controller to navigate through the menus.<br />
<br />
I tried 1942 and Mario Kart and they work great so far. Big improvement!<br />
<br />
Note: to exit games, I use Start + Hotkey (which I set to the Home button)<br />
<br />Stephen Cawoodhttp://www.blogger.com/profile/07948009840630937442noreply@blogger.com0tag:blogger.com,1999:blog-10893214.post-57551273972742135302018-02-13T11:50:00.000-08:002019-09-18T16:28:51.594-07:00How to Create a Kid-safe YouTube PlaylistNow that I've got the Beta of <a href="https://www.hivevideosports.com/" target="_blank">Hive Video</a> live, I'd like some feedback, so I'm getting the word out about some of the best use cases for this free application. While it's true that <a href="https://www.hivevideosports.com/" target="_blank">Hive Video is the simple alternative to video editing</a>, it's also true that there's more to the app than <a href="https://www.hivevideosports.com/" target="_blank">creating free highlight reel playlists from YouTube videos</a>.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-u7n9UZhlNAQ/WoM49INitTI/AAAAAAAHxZc/MraIaU3ufaMW6V3-h5UTr6eUa0Nu9imMACLcBGAs/s1600/familyTunnelVideo.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img alt="free kid-safe video playlists on tunnel video" border="0" data-original-height="1068" data-original-width="1600" height="213" src="https://1.bp.blogspot.com/-u7n9UZhlNAQ/WoM49INitTI/AAAAAAAHxZc/MraIaU3ufaMW6V3-h5UTr6eUa0Nu9imMACLcBGAs/s320/familyTunnelVideo.jpg" title="free kid-safe video playlists on tunnel video" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
Two use cases that are perfect for Hive Video are easily creating replay highlights for sports teams and kid-friendly video playlists for parents. In this post, we'll dive into the second one...<br />
<br />
As a stay-at-home dad, I'm well aware of one of the modern parenting difficulties: limiting screen time and controlling what your kids are watching. Hive Video highlight reels solve many issues:<br />
<br />
<ul>
<li>since you only add the videos you want, you always know what your kids are watching</li>
<li>you can set the start and end time of each clip, so you also know exactly which part of each video your children are seeing</li>
<li>all of the content is streaming from YouTube, but since you're not on the YouTube site (or app), there are no comments and no recommended videos</li>
</ul>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://2.bp.blogspot.com/-58gYi9IsiKA/WoNAay1O0KI/AAAAAAAHxZ4/0UYiLv3UOnQXU5Tvl2Y3M2T9-HkufMTsgCLcBGAs/s1600/tunnel-video-screen.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img alt="tunnel video free highlight reel playlists for YouTube" border="0" data-original-height="483" data-original-width="800" height="193" src="https://2.bp.blogspot.com/-58gYi9IsiKA/WoNAay1O0KI/AAAAAAAHxZ4/0UYiLv3UOnQXU5Tvl2Y3M2T9-HkufMTsgCLcBGAs/s320/tunnel-video-screen.png" title="tunnel video free highlight reel playlists for YouTube" width="320" /></a></div>
<div>
Even the YouTube Kids app does not address these issues because suggested videos are easily accessible to little fingers. It drives my wife crazy when our kids are watching a video and they start tapping around to open up other videos. Even if you're sitting next to them, they can do it too quickly for you to stop them, and then they want to watch those other videos they discovered. In our house, it's the unboxing videos that always used to come up--that is before we created Hive Video playlists.</div>
<div>
<br /></div>
<div>
<a href="https://www.hivevideosports.com/" target="_blank">Try the free Hive Video app and I recommend you register </a>(also free) so you can manage multiple highlight reels under your account.</div>
Stephen Cawoodhttp://www.blogger.com/profile/07948009840630937442noreply@blogger.com1tag:blogger.com,1999:blog-10893214.post-62734664171474471422018-01-30T13:45:00.000-08:002018-02-12T13:52:52.693-08:00AvePoint Takes Fire for Unethical MarketingHaving worked in the <a href="https://en.wikipedia.org/wiki/SharePoint" target="_blank">Microsoft SharePoint</a> space for many years, I know that it's unusual for SharePoint Independent Software Vendors (ISVs) to get much mainstream press coverage, so I was surprised to see a Washington Business article about an AvePoint marketing campaign. The article, <a href="https://www.bizjournals.com/washington/news/2018/01/18/alls-fair-in-sales-and-marketing-competitor-blogs.html" target="_blank">All's fair in sales and marketing? Competitor blogs that Metalogix is for sale, tries to poach customers</a>, highlights a shady marketing tactic recently employed by AvePoint against my former employer, <a href="https://www.metalogix.com/" target="_blank">Metalogix</a>.<br />
<br />
(Update: The Metalogix response, <a href="https://www.metalogix.com/blog/metalogix-forever" target="_blank">"Metalogix is Forever"</a> has been posted now.)<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://3.bp.blogspot.com/-vV0FN1eykQw/WoILyItxRrI/AAAAAAAHxYE/sORWt30zx9g8f4UZ3oa9IrcZubtAe7E5ACLcBGAs/s1600/Metaisforever1_0.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="350" data-original-width="700" height="160" src="https://3.bp.blogspot.com/-vV0FN1eykQw/WoILyItxRrI/AAAAAAAHxYE/sORWt30zx9g8f4UZ3oa9IrcZubtAe7E5ACLcBGAs/s320/Metaisforever1_0.png" width="320" /></a></div>
<br />
Others have written about this dishonourable marketing move (for example: <a href="https://www.linkedin.com/pulse/open-letter-avepoint-unethical-marketing-campaign-neil-mcdonnell/?trackingId=wRirZfjwCgSua8da8Kj7gg%3D%3D" target="_blank">An Open Letter to AvePoint | An Unethical Marketing Campaign</a>), and I can understand their frustration. The SharePoint partner community used to be a pretty tight-knit group focused on "coopetition." Sure, we were competing, but we also worked together to raise the profile of SharePoint--which BTW was a great strategy that paid off for everyone. As they say, 'a rising tide raises all boats.'<br />
<br />
But that's just not the case anymore, as money poured into the situation, investors took notice and the atmosphere changed. A win at all costs mentality emerged from some companies and the whole situation was a lot less interesting for many of us who started in content management before the SharePoint era. I suspect this marketing ploy will backfire.<br />
<br />
<br />
<br />
<br />Stephen Cawoodhttp://www.blogger.com/profile/07948009840630937442noreply@blogger.com0tag:blogger.com,1999:blog-10893214.post-44898479586444448052017-12-31T23:44:00.003-08:002018-12-28T22:22:16.893-08:00Hive Video - Free Highlight Reel Creator for YouTube is Live!<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://3.bp.blogspot.com/-cGw2FIZ3O2w/XCcSVFPYLWI/AAAAAAAIcQs/TQzhMzmhpc0GXxl-7n8jSEu0J95w8D_qACLcBGAs/s1600/hive_video_logo_yellow_small.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="229" data-original-width="400" height="183" src="https://3.bp.blogspot.com/-cGw2FIZ3O2w/XCcSVFPYLWI/AAAAAAAIcQs/TQzhMzmhpc0GXxl-7n8jSEu0J95w8D_qACLcBGAs/s320/hive_video_logo_yellow_small.png" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: left;">
I'm thrilled to return to blogging after a crazy few months working on my web app for <a href="http://www.youtube.com/" target="_blank">YouTube</a> videos: <a href="https://www.hivevideo.io/" target="_blank">Hive Video</a>. The Beta is live so head on over and you can create <a href="https://www.hivevideo.io/" target="_blank">free highlight reels from YouTube videos</a>.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Hive Video is <a href="https://www.hivevideo.io/" target="_blank">the simple alternative to video editing</a>. Whether you're looking to highlight the best parts of a tutorial, put together the best moments of various videos (e.g., sports), or just create a kid-friendly playlist without comments, ads, or recommended videos--it's easy, free, and doesn't require any install. </div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Once you've <a href="https://www.hivevideo.io/" target="_blank">created your highlight reel or mashup of YouTube videos</a>, you can quickly share the results with a public view URL. Send it via social media, email, or whatever, it's just plain easy.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Check it out! I'd appreciate any feedback.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="text-align: center;">My <a href="http://www.github.com/stephencawood" target="_blank">GitHub</a> heat map for 2017 tells the story pretty clearly--I started the project in April.</span></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="text-align: center;"><br /></span></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://4.bp.blogspot.com/-Svm1_0YfwOc/WknhrKXStSI/AAAAAAAHvWw/gaYNR-KRAKU8ksa46YUC1rQeDYsmr6ecACLcBGAs/s1600/GitHubCawoodHeatMap2017.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img alt="Cawood Tunnel Video GitHub heat map" border="0" data-original-height="347" data-original-width="450" src="https://4.bp.blogspot.com/-Svm1_0YfwOc/WknhrKXStSI/AAAAAAAHvWw/gaYNR-KRAKU8ksa46YUC1rQeDYsmr6ecACLcBGAs/s320/GitHubCawoodHeatMap2017.png" title="Cawood Tunnel Video GitHub heat map" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
This new project is under the umbrella of my software startup: <a href="http://www.secondmarker.com/" target="_blank">Second Marker</a>.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Update: we now support Twitch VOD videos as well.</div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<br />Stephen Cawoodhttp://www.blogger.com/profile/07948009840630937442noreply@blogger.com0tag:blogger.com,1999:blog-10893214.post-64007046252471656132017-11-14T22:38:00.000-08:002019-12-09T11:27:36.693-08:00How to View Files in a Google App Engine Docker ContainerThis post has been adapted (abridged) from the <a href="https://cloud.google.com/appengine/docs/flexible/python/debugging-an-instance" target="_blank">Google Cloud Platform (GCP) Debugging an Instance</a> documentation. I wanted to see the file structure that was being deployed to my <a href="https://angular.io/" target="_blank">Angular 5</a> application for my YouTube video highlight web app (<a href="https://www.tunnelvideo.com/" target="_blank">Tunnel Video</a>). These are the steps required.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://3.bp.blogspot.com/-8OMoDnpmlko/WoPcYc7ZBgI/AAAAAAAHxao/lpz0zVIgTNoClpK8JfGTmojrIcypB-x0gCLcBGAs/s1600/gcp_logo_lockup_cloud_platform_icon_vertical-1-e14597986174011.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="743" data-original-width="1502" height="158" src="https://3.bp.blogspot.com/-8OMoDnpmlko/WoPcYc7ZBgI/AAAAAAAHxao/lpz0zVIgTNoClpK8JfGTmojrIcypB-x0gCLcBGAs/s320/gcp_logo_lockup_cloud_platform_icon_vertical-1-e14597986174011.png" width="320" /></a></div>
<br />
<br />
1. Go to your App Engine instances in the GCP console<br />
<br />
<a href="https://console.cloud.google.com/appengine/instances">https://console.cloud.google.com/appengine/instances</a><br />
<br />
2. Find the instance and choose the SSH option to open a terminal in a browser window<br />
<br />
3. Once the terminal window for the instance opens, list the docker containers: <span style="font-family: "courier new" , "courier" , monospace;">$ sudo docker ps</span><br />
<br />
4. Find the docker container in the list with your project name in it and then run: <span style="font-family: "courier new" , "courier" , monospace;">$ container_exec containerId /bin/bash</span><br />
<br />
5. This will open a shell in your container. From there, just list your files as usual: <span style="font-family: "courier new" , "courier" , monospace;">ls app</span><br />
<br />
<br />Stephen Cawoodhttp://www.blogger.com/profile/07948009840630937442noreply@blogger.com0tag:blogger.com,1999:blog-10893214.post-86338408203802806372017-10-18T11:33:00.000-07:002018-02-15T10:17:49.804-08:00Postman with Authenticated Google API HTTP Requests<div>
Let's say I want to send an HTTP GET request to a <a href="https://developers.google.com/apis-explorer/#p/" target="_blank">Google API</a> using the super helpful <a href="https://www.getpostman.com/" target="_blank">Postman</a> application. That's simple enough, I'll just choose GET and enter the URL. In this example, I'll search <a href="http://www.youtube.com/" target="_blank">YouTube</a> for videos with "test" in their details.</div>
<div>
<br /></div>
<div>
<span style="background-color: #fafafa; color: #505050; font-size: 12px; white-space: pre-wrap;"><span style="font-family: "courier new" , "courier" , monospace;">https://www.googleapis.com/youtube/v3/search?q=test&key=AIzaSyDbnfRUVxqMEJqYwxxxxx-uV75_GaPTVr0&part=snippet&type=video</span></span></div>
<div>
<br /></div>
Breaking down this URL, you'll see that the query ("q") for "test" is in there and I'm asking for only the 'snippet' details of videos to be returned ("type=video"). Without the type optional parameter, the search would also return channels.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://4.bp.blogspot.com/-VqgPEhQKIa8/WoXNjwiAv5I/AAAAAAAHxh0/HXQ7aJycSaoK0TjQ2wuAjZqqxSKc6pLTACLcBGAs/s1600/postman_logo.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="512" data-original-width="512" height="200" src="https://4.bp.blogspot.com/-VqgPEhQKIa8/WoXNjwiAv5I/AAAAAAAHxh0/HXQ7aJycSaoK0TjQ2wuAjZqqxSKc6pLTACLcBGAs/s200/postman_logo.png" width="200" /></a></div>
<br />
<br />
The only cumbersome thing about this GET request is that I need a <a href="https://developers.google.com/maps/documentation/javascript/get-api-key" target="_blank">Google API Key</a>. These keys allow Google to limit how many API requests any account is making--and potentially charge the user when the count gets too high. To get a key, you need to a <a href="https://cloud.google.com/" target="_blank">Google Cloud Platform (GCP)</a> account and then follow the instructions to create an API key from the API Manager section.<br />
<br />
Since API keys are--by definition--limited, most people try to keep them private and restrict who can use them. There are a few different ways to add API Key Restrictions in GCP. "HTTP referrers (websites)" is a popular and straightforward option. For example, if you were using the key from a local website, you could add http://localhost:/* to the list.<br />
<br />
<b>Note:</b> If you don't care about your key being used by others, then you can leave the key with no restrictions and skip the next part.<br />
<br />
In Postman, you can pretend you're sending the request from a local website by adding a "Referer" header entry. However, since it's a restricted header, there is an extra step. You <b>must</b> turn on Postman Interceptor by clicking the Interceptor button at the top of the window (next to the sync button). If you have interceptor off, the Referer header entry will be ignored and you'll get an error: "<span style="font-family: Courier New, Courier, monospace;">Error 403:The request did not specify any referer. Please ensure that the client is sending referer or use the API Console to remove the referer restrictions.</span>"<br />
<br />
Now you're sending the GET request with an API key and you're getting back JSON results that look like this:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">{</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> "kind": "youtube#searchListResponse",</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> "etag": "\"m2yskBQFythfE4irbTIeOgYYfBU/71Y1Pa_Vox_0ZzzjdbBNppwdf0s\"",</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> "nextPageToken": "CAUQAA",</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> "regionCode": "CA",</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> "pageInfo": {</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> "totalResults": 1000000,</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> "resultsPerPage": 5</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> },</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> "items": [</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> {</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> "kind": "youtube#searchResult",</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> "etag": "\"m2yskBQFythfE4irbTIeOgYYfBU/WePlVVP0Z4fWK6zl92pA9jVLbdQ\"",</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> "id": {</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> "kind": "youtube#video",</span><br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">...</span><br />
<br />
For more about the <a href="https://developers.google.com/youtube/v3/docs/search/list" target="_blank">Google API options for YouTube Search, refer to the YouTube developer documentation</a>.<br />
<br />
Just that query alone is useful, but there's still one key thing missing from our request--authentication. I'm not going to go into detail about <a href="https://en.wikipedia.org/wiki/OAuth" target="_blank">OAuth authentication</a> here--you can read about that elsewhere--so this section will just follow the basic steps. To use the Google APIs as an authenticated user, you need an OAuth token.<br />
<br />
There are a few ways to get a Google OAuth token. Probably the simplest option is to use <a href="https://developers.google.com/oauthplayground/" target="_blank">Google's own OAuth playground</a>. This is a super useful app that allows you to fiddle with all sorts of settings.<br />
<br />
Another option is to use Postman's built in Get New Access Token feature. To use it, click on Authorization (next to the Headers tab) and then the "Get New Access Token" button. Here you would enter the Auth URL as https://accounts.google.com/o/oauth2/auth and the Access Token URL as https://accounts.google.com/o/oauth2/token. You also need to know the Scope for your request--which you probably just want to go to Google's OAuth Playground to get anyway.<br />
<br />
Once you have an access token, a simple method to check its validity is to paste this URL into a browser address bar: https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=<token></token><br />
<br />
<b>Note:</b> Checking the token will show its expiration time and also its scopes.<br />
<br />Stephen Cawoodhttp://www.blogger.com/profile/07948009840630937442noreply@blogger.com0tag:blogger.com,1999:blog-10893214.post-48172427027083421362017-09-19T12:51:00.000-07:002018-02-15T11:09:17.403-08:00Google Cloud Platform for a Full-stack Angular Web ApplicationAt first blush, getting a complete <a href="https://angular.io/" target="_blank">Angular </a>web application deployed and running on the <a href="https://cloud.google.com/" target="_blank">Google Cloud Platform (GCP)</a> can be a daunting task. Each quickstart tutorial is reasonable enough, but if you read ahead through all of the various documentation pages you'll need, they can soon begin to snowball.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-yfv2N5DsgTM/WoXaT47McqI/AAAAAAAHxiE/gLuVmXVh_cYBQp5wUiOfHeGOIt7OnWctgCLcBGAs/s1600/angular-3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="435" data-original-width="1600" height="86" src="https://1.bp.blogspot.com/-yfv2N5DsgTM/WoXaT47McqI/AAAAAAAHxiE/gLuVmXVh_cYBQp5wUiOfHeGOIt7OnWctgCLcBGAs/s320/angular-3.png" width="320" /></a></div>
<br />
<br class="Apple-interchange-newline" />
UPDATE: After I started writing this post, I discovered a Google lab that covers the same topic: <a href="https://codelabs.developers.google.com/codelabs/cloud-cardboard-viewer/index.html?index=..%2F..%2Findex#0" target="_blank">Build a Node.js & Angular Web App Using Google Cloud Platform</a>.<br />
<br />
I'll just quickly provide an overview of deploying Angular as the front-end app (on Google App Engine) and MySQL with a Node.js API on the backend. MySQL will run on Cloud SQL and Node.js runs on App Engine.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-kRFtjH4W-BI/WoXanz19jVI/AAAAAAAHxiI/fGjJlv-Nbrg547JP8QdCAfb5DRG0CkFtwCLcBGAs/s1600/nodejs.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="225" data-original-width="600" height="120" src="https://1.bp.blogspot.com/-kRFtjH4W-BI/WoXanz19jVI/AAAAAAAHxiI/fGjJlv-Nbrg547JP8QdCAfb5DRG0CkFtwCLcBGAs/s320/nodejs.png" width="320" /></a></div>
The basic steps involved are:<br />
<br />
1. Set up a GCP account and project<br />
<br />
2. <a href="https://codelabs.developers.google.com/codelabs/cloud-cardboard-viewer/index.html?index=..%2F..%2Findex#2" target="_blank">Download the Angular sample</a> and install the requirements<br />
<br />
3. Deploy your Angular front-end app to GCP<br />
<br />
4. Set up a MySQL DB on Cloud SQL. I wrote a whole post about <a href="https://geeklit.blogspot.ca/2017/08/set-up-mysql-on-google-cloud-platform.html" target="_blank">setting on MySQL on Google Cloud SQL</a>.<br />
<br />
5. Complete the tutorial for <a href="https://cloud.google.com/nodejs/getting-started/using-cloud-sql" target="_blank">using Google Cloud with Node.js</a><br />
<br />
Since these samples aren't linked, you'll have to test the pieces separately until you develop some interaction in your Angular app. You can use Postman to test your Node.js API.Stephen Cawoodhttp://www.blogger.com/profile/07948009840630937442noreply@blogger.com0tag:blogger.com,1999:blog-10893214.post-29639912995225189752017-08-14T10:07:00.000-07:002018-02-15T10:09:23.652-08:00Set up MySQL on Google Cloud PlatformHere's a walkthrough of how to set up <a href="https://www.mysql.com/" target="_blank">MySQL</a> on <a href="https://cloud.google.com/" target="_blank">Google Cloud Platform's (GCP)</a> <a href="https://cloud.google.com/sql/" target="_blank">Cloud SQL</a> service.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-T9YvUj21qZM/WoXMRXSGTPI/AAAAAAAHxho/uwOprQjPI-U6wE6kkXUcd0MgFfYybW6jACLcBGAs/s1600/cloud_sql_gcp.jpeg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="200" data-original-width="200" src="https://1.bp.blogspot.com/-T9YvUj21qZM/WoXMRXSGTPI/AAAAAAAHxho/uwOprQjPI-U6wE6kkXUcd0MgFfYybW6jACLcBGAs/s1600/cloud_sql_gcp.jpeg" /></a></div>
<br />
<br />
1. Create a Google account if you don't have one: <a href="https://accounts.google.com/">https://accounts.google.com</a><br />
<br />
2. Sign up for a free trial on: <a href="https://console.cloud.google.com/">https://console.cloud.google.com</a><br />
<br />
3. Create a GCP project to house your MySQL instance.<br />
<br />
4. Create a Cloud SQL instance--obviously, choosing MySQL as the DB type.<br />
<br />
5. Create a database in the instance by going to the Databases tab under the Instance. For this post, I'll create one called "test".<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://3.bp.blogspot.com/-8OMoDnpmlko/WoPcYc7ZBgI/AAAAAAAHxas/wx2UprskHDs_YzP1sPtBPgjILZM8EhDEgCEwYBhgL/s1600/gcp_logo_lockup_cloud_platform_icon_vertical-1-e14597986174011.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="743" data-original-width="1502" height="158" src="https://3.bp.blogspot.com/-8OMoDnpmlko/WoPcYc7ZBgI/AAAAAAAHxas/wx2UprskHDs_YzP1sPtBPgjILZM8EhDEgCEwYBhgL/s320/gcp_logo_lockup_cloud_platform_icon_vertical-1-e14597986174011.png" width="320" /></a></div>
<br />
<br />
6. To add a schema to your DB, you can use the Cloud SQL command line, <a href="https://dbdesigner.net/">https://dbdesigner.net</a> or <a href="https://www.mysql.com/products/workbench" target="_blank">MySQL Workbench</a>. Of course, if you know SQL, you can just type out your schema, but if you want a visual representation of your DB, these tools are handy.<br />
<br />
I like the simplicity and no-install aspect of dbdesginer.net, but I ran into a bug, so I don't use it as much as I would otherwise. Hopefully, they'll fix it soon. Until then, I guess I have to recommend MySQL Workbench since it's great in many ways.<br />
<br />
7. If you just want to quickly test that your DB is working. You can run some simple queries in the Google Cloud Shell. To connect to your MySQL instance, open the shell (little command line icon in the top-right of the GCP dashboard.) and type in these commands:<br />
<br />
googleusername@projectId:~$ gcloud beta sql connect dbinstance --user=dbusername (usually root for username)<br />
<br />
You'll be prompted for your password and then you'll see the MySQL prompt. Note that it might take a few seconds before it appears.<br />
<br />
Whitelisting your IP for incoming connection for 5 minutes...\<br />
Enter password:<br />
<br />
Welcome to the MySQL monitor. Commands end with ; or \g.<br />
Your MySQL connection id is 1234<br />
Server version: 5.7.14-google-log (Google)<br />
Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.<br />
Oracle is a registered trademark of Oracle Corporation and/or its<br />
affiliates. Other names may be trademarks of their respective<br />
owners.<br />
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.<br />
mysql>;<br />
<br />
8. To run your test queries, you'll need to select your DB within your instance.<br />
<br />
mysql>; use test<br />
Database changed<br />
<br />
Once you've selected the DB, you can run a simple operation to create a table and then use SHOW TABLES; to see the results.<br />
<br />
The command I'll use to create a simple table is:<br />
<br />
CREATE TABLE `testtable` (<br />
<span style="white-space: pre;"> </span>`id` INT NOT NULL AUTO_INCREMENT,<br />
<span style="white-space: pre;"> </span>PRIMARY KEY (`id`)<br />
);<br />
<br />
This creates a new table with one column called "id".<br />
<br />
And this is what it looks like when I paste this command into the Cloud Shell:<br />
<br />
mysql>; CREATE TABLE `testtable` (<br />
>; `id` INT NOT NULL AUTO_INCREMENT,<br />
>; PRIMARY KEY (`id`)<br />
>; );<br />
Query OK, 0 rows affected (0.03 sec)<br />
mysql>; show tables;<br />
+----------------+<br />
| Tables_in_test |<br />
+----------------+<br />
| testtable |<br />
+----------------+<br />
1 row in set (0.00 sec)<br />
mysql>;<br />
<br />
9. Now that you have a working DB, you'll want to connect to it from code and possibly from a MySQL client (e.g., the 'dolphin' a.k.a. MySQL Workbench).<br />
<br />
To get the info you need, go to the Instance Details page in GCP and look at the Properties under the Overview tab. What you need is the "IPv4 address". Grab that, but you won't be able to connect to your instance yet. First, you have to allow connections.<br />
<br />
To allow your client to connect to your DB in the Google cloud, you need to open Authorized Networks and add your network IP. To get your client IP, type "what's my IP" into Google. <br />
<br />
10. Now you can access your DB from a MySQL client, but what about a local dev instance of a GCP App Engine application? For that you need a <a href="https://cloud.google.com/sql/docs/mysql/sql-proxy" target="_blank">Google Cloud SQL Proxy</a> running.<br />
<br />
NOTE: The default setting is for all apps within the same project to be authorized, if you're using the DB from a different GCP project, you'll have to authorize it explicitly.<br />
<br />
Without the Cloud SQL Proxy running, your local dev instance of your app will return the error: Error: connect ECONNREFUSED xxx.0.0.xxx:3306.<br />
<br />
You'll need to install the <a href="https://cloud.google.com/sdk/gcloud/reference/config/set" target="_blank">Google Cloud SDK</a> (configuration will have you select the project). If you have multiple environments [e.g., test, dev, prod, etc. You may have to change this as some point with this command: cawood$ gcloud config set project projectName]), and enable the Cloud SQL API, if you haven't already. You'll also need a credential file to supply to the proxy. To get this file, create a service account in the IAM & Admin section of GCP. Make sure you grant the account rights to Cloud SQL. When you create the account, you'll be able to create a new private key and download a JSON credential file. The Cloud Proxy will read this credential file.<br />
<br />
https://cloud.google.com/sql/docs/mysql/connect-external-app<br />
<br />
11. Once you have everything ready, here is the command to <a href="https://cloud.google.com/sql/docs/mysql/connect-admin-proxy" target="_blank">start the Cloud Proxy</a>:<br />
./cloud_sql_proxy -instances=instanceConnectionName=tcp:3306 \ -credential_file=credentials.json &<br />
<br />
If all goes well, the Cloud Proxy command will return this output:<br />
<br />
cawood$ 2017/04/23 22:34:39 Listening on 127.0.0.1:3306 for instanceConnectionName<br />
2017/04/23 22:34:39 Ready for new connections<br />
<br />
<br />
FYI: From the command line help:<br />
Authorization:<br />
* On Google Compute Engine, the default service account is used.<br />
The Cloud SQL API must be enabled for the VM.<br />
<br />
* When gcloud is installed on the local machine, the "active account" is used<br />
for authentication. Run 'gcloud auth list' to see which accounts are<br />
installed on your local machine and 'gcloud config list account' to view<br />
the active account.<br />
<br />
* To configure the proxy using a service account, pass the -credential_file<br />
flag or set the GOOGLE_APPLICATION_CREDENTIALS environment variable. This<br />
will override gcloud or GCE credentials (if they exist).<br />
<br />Stephen Cawoodhttp://www.blogger.com/profile/07948009840630937442noreply@blogger.com1tag:blogger.com,1999:blog-10893214.post-11392690013293069702017-07-11T08:20:00.000-07:002018-01-02T09:05:24.236-08:00Second Marker SoftwareMy next project has begun. I’m working on a software startup called <a href="http://www.secondmarker.com/" target="_blank">Second Marker</a>.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://www.secondmarker.com/" target="_blank"><img alt="Second Marker Software" border="0" data-original-height="1095" data-original-width="1600" height="219" src="https://1.bp.blogspot.com/-WIqWtvj99mA/Wku5v9A9XJI/AAAAAAAHvYQ/aHknFn---vkDaBxZBap9af47JoVm5k_-ACLcBGAs/s320/secondMarker_logo_text.png" title="Second Marker Software" width="320" /></a></div>
<br />
<br />
Our first solution—and the inspiration for starting the company—is a free and easy alternative to video editing: <a href="http://www.tunnelvideo.com/" target="_blank">Tunnel Video</a>. If you want to make a <a href="http://www.tunnelvideo.com/" target="_blank">highlight reel of YouTube videos</a>, this will be the simplest solution.<br />
<br />
There are endless possibilities for why someone would want to create and <a href="http://www.tunnelvideo.com/" target="_blank">share a playlist of YouTube video with start and end times</a>, but some use cases include:<br />
<br />
<ul>
<li><a href="http://www.tunnelvideo.com/" target="_blank">Playlists of training videos for athletes and coaches</a></li>
<li><a href="http://www.tunnelvideo.com/" target="_blank">Highlights of tutorial videos</a></li>
<li><a href="http://www.tunnelvideo.com/" target="_blank">Easier sharing of multiple videos</a></li>
<li><a href="http://www.tunnelvideo.com/" target="_blank">Kid-friendly playlists with none of the usual YouTube complications such as comments and recommended videos</a></li>
</ul>
Update: try the Beta for free at <a href="http://www.tunnelvideo.com/">http://www.tunnelvideo.com</a>!Stephen Cawoodhttp://www.blogger.com/profile/07948009840630937442noreply@blogger.com0tag:blogger.com,1999:blog-10893214.post-9967309960901407172017-06-21T20:43:00.000-07:002017-08-06T21:23:00.775-07:00Google Cloud SQL Proxy on Same Machine as MySQLAs a web developer, it makes sense that you'd want to connect to my <a href="https://cloud.google.com/sql/" target="_blank">Google Cloud SQL</a> instances running in <a href="https://cloud.google.com/" target="_blank">Google Cloud Platform (GCP)</a> via <a href="https://cloud.google.com/sql/docs/mysql/connect-admin-proxy" target="_blank">Cloud SQL Proxy</a> and also have <a href="https://www.mysql.com/" target="_blank">MySQL</a> running locally on your machine. Unfortunately, the default configurations for these two systems will cause an error when you try to run the proxy: "bind: address already in use."<br />
<br />
One clear reason to run the proxy is that it allows you to securely connect to your Cloud SQL instances with <a href="https://www.mysql.com/products/workbench/" target="_blank">MySQL Workbench</a>. You can get the IP (hostname) and port from GCP no problem, but to connect, you need to be running Google's proxy. (You also need to create a JSON credential file from GCP and add your IP to the Authorized Networks BTW.)<br />
<br />
The reason for the error is that both the proxy and the local default install of MySQL will try to communicate on port 3306. When you try to start the proxy, this is the result:<br />
<br />
<style type="text/css"> p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #000000; background-color: #ffffff} span.s1 {font-variant-ligatures: no-common-ligatures} </style>
<br />
<div class="p1">
<span class="s1"><span style="font-family: Courier New, Courier, monospace; font-size: small;">doom:project cawood$ ./cloud_sql_proxy -instances=instancename-9999:us-west1:instance=tcp:3306 -credential_file=CredFileName-dflkjal.json &</span></span></div>
<div class="p1">
<span class="s1"><span style="font-family: Courier New, Courier, monospace; font-size: small;"><br /></span></span></div>
<span style="font-family: Courier New, Courier, monospace;">doom:project cawood$ using credential file for authentication; email=test@test.999999.iam.gserviceaccount.com</span><br />
<span style="font-family: Courier New, Courier, monospace;">2017/06/05 23:51:24 listen tcp 127.0.0.1:3306: bind: address already in use</span><br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: inherit;">To resolve this error, one solution is to create a config file for the local MySQL server and change the port.</span><br />
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;">One thing to note (as per the <a href="https://dev.mysql.com/doc/refman/5.7/en/server-configuration-defaults.html" target="_blank">MySQL documentation</a>) is that the installer for macOS will not create a config file: "</span>Note As of MySQL 5.7.18, my-default.cnf is no longer included in or installed by distribution packages." If you don't have a config file already... to create the config file, simply run these commands.<br />
<br />
<style type="text/css"> p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #000000; background-color: #ffffff} span.s1 {font-variant-ligatures: no-common-ligatures} </style>
<br />
<div class="p1">
<span class="s1"><span style="font-family: Courier New, Courier, monospace; font-size: small;">$ cd /etc</span></span></div>
<div class="p1">
<span class="s1"><span style="font-family: Courier New, Courier, monospace; font-size: small;"> <style type="text/css"> p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #000000; background-color: #ffffff} span.s1 {font-variant-ligatures: no-common-ligatures} </style> </span></span></div>
<div class="p1">
<span class="s1"><span style="font-family: Courier New, Courier, monospace; font-size: small;">$ sudo nano my.cnf</span></span></div>
<div class="p1">
<span class="s1"><span style="font-family: Courier New, Courier, monospace; font-size: small;"><br /></span></span></div>
<span style="font-family: inherit; font-size: small;"><span style="font-variant-ligatures: no-common-ligatures;">This is all you need in the file:</span></span><br />
<span style="font-family: inherit; font-size: small;"><span style="font-variant-ligatures: no-common-ligatures;"><br /></span></span>
<div class="p1">
<span style="font-family: Courier New, Courier, monospace; font-size: small;"><span style="font-variant-ligatures: no-common-ligatures;">[client]</span></span></div>
<div class="p1">
<span style="font-family: Courier New, Courier, monospace; font-size: small;"><span style="font-variant-ligatures: no-common-ligatures;">port = 3366</span></span></div>
<div class="p1">
<span style="font-family: Courier New, Courier, monospace; font-size: small;"><span style="font-variant-ligatures: no-common-ligatures;"><br /></span></span></div>
<div class="p1">
<span style="font-family: Courier New, Courier, monospace; font-size: small;"><span style="font-variant-ligatures: no-common-ligatures;">[mysqld]</span></span></div>
<div class="p1">
<span class="s1"><span style="font-family: Courier New, Courier, monospace; font-size: small;"></span></span></div>
<div class="p1">
<span style="font-family: Courier New, Courier, monospace; font-size: small;"><span style="font-variant-ligatures: no-common-ligatures;">port = 3366</span></span></div>
<div class="p1">
<span style="font-family: Courier New, Courier, monospace; font-size: small;"><span style="font-variant-ligatures: no-common-ligatures;"><br /></span></span></div>
<span style="font-family: inherit;"><span style="font-size: small;"><span style="font-variant-ligatures: no-common-ligatures;">Once you're done use </span></span><span style="font-size: small; font-variant-ligatures: no-common-ligatures;">CTRL + o to save and exit Nano.</span></span><br />
<span style="font-variant-ligatures: no-common-ligatures;"><span style="font-family: Courier New, Courier, monospace; font-size: small;"><br /></span></span><span style="font-variant-ligatures: no-common-ligatures;"><span style="font-family: inherit; font-size: small;">It's worth noting, that I used to see this error before I installed MySQL locally. The reason was that there was another Cloud SQL Proxy process ("cloud_sql_proxy") already running. The solution to that is simply to kill the process.</span></span>Stephen Cawoodhttp://www.blogger.com/profile/07948009840630937442noreply@blogger.com0tag:blogger.com,1999:blog-10893214.post-31602616223616134112017-05-04T11:09:00.000-07:002018-02-15T11:12:01.235-08:00May the Fourth be with YouI don't have a post for May, so here it is...<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-rzA7TBF_u0c/WoXbR847efI/AAAAAAAHxig/n2ub1OiI0tY9wMSJu6LgPf2aH1tBN2yLwCLcBGAs/s1600/darthvader_beachwaterjug.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1600" data-original-width="1134" height="320" src="https://1.bp.blogspot.com/-rzA7TBF_u0c/WoXbR847efI/AAAAAAAHxig/n2ub1OiI0tY9wMSJu6LgPf2aH1tBN2yLwCLcBGAs/s320/darthvader_beachwaterjug.jpg" width="226" /></a></div>
<br />Stephen Cawoodhttp://www.blogger.com/profile/07948009840630937442noreply@blogger.com0tag:blogger.com,1999:blog-10893214.post-88673300084693333052017-04-12T14:05:00.000-07:002017-05-24T14:13:02.274-07:00Node.js Authentication Error Connecting to Google Cloud SQL ProxyI was receiving this error trying to run a local instance of a <a href="https://nodejs.org/en/">Node.js</a> API against a <a href="https://cloud.google.com/">Google Cloud Platform (GCP)</a> database.<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">Error: ER_ACCESS_DENIED_ERROR: Access denied for user ''@'cloudsqlproxy~174.7.116.43' (using password: NO) at Handshake.Sequence._packetToError (/Users/cawood/GitHub/project/node_modules/mysql/lib/protocol/sequences/Sequence.js:52:14) at Handshake.ErrorPacket (/Users/cawood/GitHub/project/node_modules/mysql/lib/protocol/sequences/Handshake.js:103:18) at emitOne (events.js:96:13) at Socket.emit (events.js:191:7) at readableAddChunk (_stream_readable.js:178:18) at Socket.Readable.push (_stream_readable.js:136:10) </span><br />
<br />
The solution was quite straightforward, but when I first googled the error, I couldn't find anything about the access denied error showing no user--the username should be in the error (i.e. 'username'@'cloudsqlproxy).<br />
<br />
I thought the error was the <a href="https://cloud.google.com/sql/docs/mysql/sql-proxy">GCP Cloud SQL Proxy</a>, but I was initiating it correctly and with a valid credential file for a service account:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">$ ./cloud_sql_proxy -instances=name-111111:us-central1:instancename=tcp:0000 \ -credential_file=serviceAccountCreds.json &</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span> <span style="font-family: inherit;">The problem was actually with the environment variables for my local instance of the Node.js API. I hadn't exported them. Of course, the error was correct, I wasn't trying to connect with any user at all.</span><br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">$ export MYSQL_USER="username" </span><br />
<span style="font-family: "courier new" , "courier" , monospace;">$ export MYSQL_PASSWORD="password" </span><br />
<span style="font-family: "courier new" , "courier" , monospace;">$ export MYSQL_DATABASE="test" </span><br />
<span style="font-family: "courier new" , "courier" , monospace;">$ npm start</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span><span style="font-family: inherit;"><span style="font-family: inherit;">To check if you have set these correctly, you can output them to the console when you start the server</span>:<span style="font-family: Courier New, Courier, monospace;"> console.log(config);</span></span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span><span style="font-family: "courier new" , "courier" , monospace;"> cawood$ npm start </span><br />
<span style="font-family: "courier new" , "courier" , monospace;">> projectapi@0.0.1 start /Users/cawood/GitHub/project</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">> node server.js </span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><span style="color: blue;"> { user: 'username', password: 'password', database: 'test' }</span> </span><br />
<span style="font-family: "courier new" , "courier" , monospace;">App listening on port 8000 Press Ctrl+C to quit.</span>Stephen Cawoodhttp://www.blogger.com/profile/07948009840630937442noreply@blogger.com0tag:blogger.com,1999:blog-10893214.post-67028800595475904112017-03-10T14:03:00.000-08:002017-03-10T14:14:53.297-08:00Google Acquires AppBridge SoftwareAt the <a href="https://cloudnext.withgoogle.com/" target="_blank">Google Cloud Next conference</a> yesterday it was announced that <a href="https://www.blog.google/products/g-suite/introducing-new-enterprise-ready-tools-google-drive/" target="_blank">Google has acquired AppBridge</a>. I've been at <a href="https://www.appbridge.io/" target="_blank">AppBridge</a> for just over a year and I'm thrilled to be a part of this Vancouver software success story.<br />
<br />
AppBridge is a tremendous addition to the Google solutions because the founders designed the <a href="https://www.appbridge.io/transformation-suite" target="_blank">AppBridge Transformation Suite</a> from the ground up for performance and the cloud. AppBridge is already handling the world's largest <a href="https://gsuite.google.com/" target="_blank">G Suite</a> and <a href="https://www.google.com/drive/" target="_blank">Google Drive</a> migrations.<br />
<br />
<img src="https://3.bp.blogspot.com/-EkBXS5l1w70/WMMbzLKtnII/AAAAAAAHMNM/aDhtB6HndwEqHCpemqHj7TbAiTAjOWkuACK4B/s320/AppBridgeGoogle.png" width="400" />
<br />
<br />
Here is a quote from Google's announcement:<br />
<br />
"Migrating to the cloud can be complex. It's not just your files that need to be moved; permissions also need to map correctly; content likely needs to be reorganized, and some data probably needs to be archived. To address that challenge, today we are announcing the acquisition of AppBridge, an enterprise-grade, G Suite migration tool that helps organizations seamlessly migrate from their on-prem, cloud-based and hybrid solutions to Google Drive.<br />
<br />
With AppBridge, your organization can migrate files effortlessly to G Suite from your existing file servers or content management systems like SharePoint, or from many other cloud platforms you might be using. File permissions are also brought over when you migrate, which means your team's file access remains unchanged and your data stays safe. We’re working together with AppBridge to bring them into the G Suite team. Stay tuned for more information in the near future."<br />
<br />
<div class="separator" style="clear: both; text-align: left;">
<a href="https://4.bp.blogspot.com/-XYBfuBtsEXM/WMMhzXK1S2I/AAAAAAAHMPk/-IDapiXOG6QP4l9WoL8Y6UuzMT_QWxvxQCLcB/s1600/AppBridge-Logo.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://4.bp.blogspot.com/-XYBfuBtsEXM/WMMhzXK1S2I/AAAAAAAHMPk/-IDapiXOG6QP4l9WoL8Y6UuzMT_QWxvxQCLcB/s320/AppBridge-Logo.png" width="400" /></a></div>
<br />
<br />
As for me, I'm going to take some time to think about what I want for my next adventure, but this one was certainly memorable.<br />
<br />
TechCrunch Article: <a href="https://techcrunch.com/2017/03/09/google-acquires-appbridge-to-help-enterprises-move-their-files-to-its-cloud-services/" target="_blank">Google acquires AppBridge to help enterprises move their files to its cloud services</a>Stephen Cawoodhttp://www.blogger.com/profile/07948009840630937442noreply@blogger.com0tag:blogger.com,1999:blog-10893214.post-73803570386680910072017-02-15T20:05:00.000-08:002017-02-16T00:22:57.831-08:00Google Sheets Split into Columns<br />
I'm very much appreciating <a href="https://support.google.com/docs/answer/6325535?hl=en" target="_blank">Google Sheets' split into columns feature</a>. Ya, I know, it's not like it hasn't been done before, but man it's nice to have when you need it.<br />
<br />
<div>
<a href="http://3.bp.blogspot.com/-b5h9lzTRmt8/WKTROrXyC2I/AAAAAAAHKg0/liiK1MZbTh0amTtV8OZpYDA1Z90wkCvDQCK4B/s1600/GoogleSheetssplit-text-to-columns.gif" imageanchor="1"><img border="0" src="https://3.bp.blogspot.com/-b5h9lzTRmt8/WKTROrXyC2I/AAAAAAAHKg0/liiK1MZbTh0amTtV8OZpYDA1Z90wkCvDQCK4B/s320/GoogleSheetssplit-text-to-columns.gif" width="400" /></a></div>
<div>
<br /></div>
<div>
To split text into columns using separator options (including space, comma, custom, etc.):</div>
<ol>
<li>Open a spreadsheet in <a href="https://www.google.ca/sheets/about/" target="_blank">Google Sheets</a>.</li>
<li>Paste the data you want to split into columns.</li>
<li>In the bottom right corner of your data, click the Paste icon.</li>
<li>Click Split text to columns. Your data will split into different columns.</li>
<li>To change the delimiter, in the separator box, click Comma to open the dropdown of options.</li>
</ol>
Stephen Cawoodhttp://www.blogger.com/profile/07948009840630937442noreply@blogger.com0tag:blogger.com,1999:blog-10893214.post-74510440702406608852017-01-18T18:20:00.001-08:002023-01-31T11:31:46.701-08:00Building Video Games with My Daughter and LEGOI asked my (then) 4-year-old daughter if she wanted to make a video game, and her response was exactly what I expected, "We can't make a game." "Actually, " I said, "we can! Want to do it?" She emphatically said yes, so we set out to make an iOS game. Of course, I had already done some playing around with <a href="https://unity3d.com/" target="_blank">Unity 3D</a>, so I knew that a 2D game wouldn't be too much work.<br />
<br />
I wanted her to have the best experience possible, so I devised a plan to take her ideas and convert them into the game without changing much along the way. I used a sample 2D game as a starting point so we wouldn't have to spend too much time writing code before she saw her creations in a working game. Note that if you don't deploy your app to the store, you can do all of this for free (plus the cost of LEGO of course).<br />
<br />
We started by drawing out the main elements of the game by hand. Here are the villain and hero characters as my daughter drew them.<br />
<br />
<a href="http://2.bp.blogspot.com/-D3rH0F3_cpM/WIAR5FS_0QI/AAAAAAAHIZA/BJpk80lp2hckYKi7tTfoKgu25EzHaKXjgCK4B/s1600/RobinsConceptPlayerAndEnemySprites.jpg" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-right: 1em; text-align: center;"><img border="0" height="240" src="https://2.bp.blogspot.com/-D3rH0F3_cpM/WIAR5FS_0QI/AAAAAAAHIZA/BJpk80lp2hckYKi7tTfoKgu25EzHaKXjgCK4B/s320/RobinsConceptPlayerAndEnemySprites.jpg" width="320" /></a><br />
<br />
After that, it was off to the <a href="https://www.lego.com/" target="_blank">L</a>EGO store so we could get enough 2x2 pieces to build whatever we wanted. Here's the villain LEGO version my daughter created from her original drawing.<br />
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<a href="http://3.bp.blogspot.com/-ivA-QizHmAI/WIASAFJEp-I/AAAAAAAHIZI/NnrBJj0FOqIbEkiFsQBoeJ6G7QG63nu8wCK4B/s1600/Villian1LegoConceptHead.jpg" imageanchor="1"><img border="0" height="320" src="https://3.bp.blogspot.com/-ivA-QizHmAI/WIASAFJEp-I/AAAAAAAHIZI/NnrBJj0FOqIbEkiFsQBoeJ6G7QG63nu8wCK4B/s320/Villian1LegoConceptHead.jpg" width="240" /></a><br />
<br />
We designed the rest of the game elements including a tree and an egg, and then I used photos of the LEGO concepts to convert her creations to simple sprite versions. Here's another character before reducing the pixels down to a simple sprite.<br />
<br />
<a href="http://4.bp.blogspot.com/-9rhsFI07d3w/WIAVd5lO-hI/AAAAAAAHIZw/UHxqClhyHxgv6tSvvLUyX8yfknWDvgL6ACK4B/s1600/20150705_221315083_iOS.jpg" imageanchor="1"><img border="0" height="320" src="https://4.bp.blogspot.com/-9rhsFI07d3w/WIAVd5lO-hI/AAAAAAAHIZw/UHxqClhyHxgv6tSvvLUyX8yfknWDvgL6ACK4B/s320/20150705_221315083_iOS.jpg" width="240" /></a><br />
<br />
My daughter building LEGO versions of the game sprites.<br />
<br />
<a href="http://2.bp.blogspot.com/-ttg-fqEmINM/WIAST6yAHII/AAAAAAAHIZg/OG3OD-bYIII1X6ZdLxp6qgy2Rkn3mBGJQCK4B/s1600/RobinEggsiosVideoGameLegoSprite.png" imageanchor="1"><img border="0" height="320" src="https://2.bp.blogspot.com/-ttg-fqEmINM/WIAST6yAHII/AAAAAAAHIZg/OG3OD-bYIII1X6ZdLxp6qgy2Rkn3mBGJQCK4B/s320/RobinEggsiosVideoGameLegoSprite.png" width="240" /></a><br />
<br />
This is a screenshot from the first build I deployed to my iPhone (just for fun, I threw in a photo of my daughter as the player). Not bad for a 4-year-old with some LEGO!<br />
<div>
</div>
<div>
<br /></div>
<div>
<a href="http://1.bp.blogspot.com/-6TT-3A6CZnw/WIAV8BR2j6I/AAAAAAAHIZ4/-3eIGBP4YtAmUZL92cR4KAEhdk61vU8MgCK4B/s1600/ScreenRobinHead1024x768.png" imageanchor="1"><img border="0" height="320" src="https://1.bp.blogspot.com/-6TT-3A6CZnw/WIAV8BR2j6I/AAAAAAAHIZ4/-3eIGBP4YtAmUZL92cR4KAEhdk61vU8MgCK4B/s320/ScreenRobinHead1024x768.png" width="318" /></a></div>
<div>
<br /></div>
<div>
We had a great time on this project and I was prompted to write this post when she recently asked if we could play the game again.</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="http://2.bp.blogspot.com/-D3rH0F3_cpM/WIAR5FS_0QI/AAAAAAAHIZA/BJpk80lp2hckYKi7tTfoKgu25EzHaKXjgCK4B/s1600/RobinsConceptPlayerAndEnemySprites.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><br /></a></div>
Stephen Cawoodhttp://www.blogger.com/profile/07948009840630937442noreply@blogger.com0