{"title":"Stanley Solutions Blog - development","link":[{"@attributes":{"href":"https:\/\/blog.stanleysolutionsnw.com\/","rel":"alternate"}},{"@attributes":{"href":"https:\/\/blog.stanleysolutionsnw.com\/feeds\/development.atom.xml","rel":"self"}}],"id":"https:\/\/blog.stanleysolutionsnw.com\/","updated":"2024-11-26T10:35:00-08:00","subtitle":"engineering and creativity - all under one hat","entry":[{"title":"A Collaborative Word Cloud for Teaching","link":{"@attributes":{"href":"https:\/\/blog.stanleysolutionsnw.com\/a-collaborative-wordcloud-for-teaching.html","rel":"alternate"}},"published":"2024-11-26T10:35:00-08:00","updated":"2024-11-26T10:35:00-08:00","author":{"name":"Joe Stanley"},"id":"tag:blog.stanleysolutionsnw.com,2024-11-26:\/a-collaborative-wordcloud-for-teaching.html","summary":"<p>A few months ago I wrapped up some work on a fun little project I developed for some teaching exercises. Let me tell you about it!<\/p>","content":"<p>It started back in June when I was frantically preparing some exercises to teach and prepare a group of the College Staff to support Idaho's\n<a href=\"https:\/\/www.uidaho.edu\/extension\/4h\/events\/stac\">State Teen Association Convention<\/a>. I was trying to prepare some training to align the team.\nThat's not something I've done before, and I can't make any claims that it's perfect, but I think it was well received.<\/p>\n<p>Let me explain what that exercise really was...<\/p>\n<p>I used a handful of different materials to try making it both fun and engaging. Some of those materials were from National 4-H's\n<a href=\"https:\/\/www.4-h.org\/wp-content\/uploads\/2024\/04\/09105552\/4H-Harmony-Guide-2024.pdf\">\"Meet Up Buddy\"<\/a> card activity. Some were of my own\ncreation to encourage the College Staff to think about how to prioritize things when time constraints pressed them. My alignment training\nwas where we started, it was basically a challenge to the participants. Here's how I set it up, though, maybe not in so many well-placed words.<\/p>\n<blockquote>\n<p>4-H is full of projects. So many things for youth to engage with and to find their spark. While this is wonderful, it does pose a bit of a\nchallenge. Before I explain, I want you to think about all the words that come to mind when you think of 4-H.<\/p>\n<\/blockquote>\n<p>At this point, I'd share a QR code with them that they could use to navigate to the game page. The game page provides participants with a\ntext box where they can enter each word that they think about. When they enter a word, a new text box appears so they can enter another word.\nAs they'd enter words, my system keeps track of them (hidden, of course) and begins to create a word cloud with all of their entries.<\/p>\n<p>When I decide that they've had enough time (normally it looks like they've run out of ideas for words), then I shut down the entry system and\nreveal the word cloud generated from all of their entries. At the end of it all, we're left with something like this:<\/p>\n<p><img src=\"https:\/\/immich.stanleysolutionsnw.com\/api\/assets\/5692b76b-9049-4ebc-b0b8-4812c751716a\/thumbnail?size=preview&key=klMHZEmZIUXG5UF9NeiFfKmLCZOcCm99k9mdI6Rw_U9TbJhC6zepXq-tEantGNrj748\" style=\"width: 100%;\" alt=\"college staff word cloud\"><\/p>\n<p>That gives us something to talk about.<\/p>\n<blockquote>\n<p>Look at this. These are all the things that our little group thinks about when we think about 4-H. But what do we share?<\/p>\n<p>Often, we're working in our own little silos, not terribly focussed on the same things that other leaders are focussed on. That can make\nworking with other leaders difficult. We're each moving in our own direction, and sometimes, that feels like pulling a ball of yarn from\ndifferent ends; just making the knot perpetually tighter.<\/p>\n<p>But this shows us something. Even when we are moving in different directions, it's possible to find some common ground. These are the first\nthings that <em>you<\/em> thought of when I asked you to think about 4-H. These are the inately tied to your path in 4-H, and see how we have\nsomething common shared between us?<\/p>\n<\/blockquote>\n<p>At that point, I'd talk about why I think it's good to get a common understanding of what is shared between the group, and how I like to think\nabout using this commonality with people to find shared ground on certain topics. To move forward together on what we see as the shared pathway.<\/p>\n<p>It worked pretty well for our little team, I was able to refer back to it a number of times, and it made working with the team all the more\npleasant and constructive.<\/p>\n<hr>\n<p>The exercise wasn't purely without its challenges, however. In fact, we discovered that the web-service that I'd chosen to use only allowed\nsomething like 10 players, and each player could only enter 15 words. Not exactly the sort of restrictions you want to run into during an\nexercise. Add to that, the fact that you've got to have a solid internet connection, which is not always easy in 4-H programming in the state\nof Idaho.<\/p>\n<p>That's why I returned to my recent build, the <a href=\"\/making-portable-digital-learning.html\">PortaServer<\/a>. My little (not so little, actually) ammo\ncan with a stack of mini computers. I could serve my own system there, and since I run the network, I could also make the address very easy\nto access.<\/p>\n<blockquote>\n<p>Enter: <a href=\"https:\/\/github.com\/engineerjoe440\/wordwall\">WordWall<\/a><\/p>\n<\/blockquote>\n<p>It's pretty simple in concept. A little more effort in practice, but that's alright. I made a Python FastAPI server with a tiny, ephemeral\nSQLite database backing it to run all the management of adding, removing, and sharing words. Tie that with a Material-UI based React frontend,\nand I've got a relatively simple little service that I can run on my own system.<\/p>\n<p>Now, I've got something I can take with me, wherever I travel to align 4-H leaders, youth, or others.<\/p>","category":[{"@attributes":{"term":"development"}},{"@attributes":{"term":"python"}},{"@attributes":{"term":"react"}},{"@attributes":{"term":"teaching"}},{"@attributes":{"term":"youth"}},{"@attributes":{"term":"software"}},{"@attributes":{"term":"development"}},{"@attributes":{"term":"fastapi"}},{"@attributes":{"term":"material-ui"}}]},{"title":"Basic Auth without the Hubub in NGINX","link":{"@attributes":{"href":"https:\/\/blog.stanleysolutionsnw.com\/basic-auth-without-the-hubub-in-nginx.html","rel":"alternate"}},"published":"2024-11-08T20:49:00-08:00","updated":"2024-11-08T20:49:00-08:00","author":{"name":"Joe Stanley"},"id":"tag:blog.stanleysolutionsnw.com,2024-11-08:\/basic-auth-without-the-hubub-in-nginx.html","summary":"<p>Today I learned that it IS possible to make a relatively simple static site that has some automatic authentication built in to NGINX that will use a custom HTML file for the login page as opposed to the basic-auth prompt that gets so annoying.<\/p>","content":"<p>Okay... As I write this, I'm falling asleep. Hah! So I'll make this quick.<\/p>\n<p>I wanted a website that:<\/p>\n<ul>\n<li>was a static site (no ORM, database, etc.)<\/li>\n<li>had some basic authentication (yeah, BasicAuth is fine)<\/li>\n<li>had a custom login page with my own styled HTML\/CSS<\/li>\n<\/ul>\n<p>Now, I've known that NGINX allows users to configure basic auth for some time.\nI've done that, before. But I didn't know how to get it to do the custom HTML in\nconjunction with that. Here's what I did.<\/p>\n<p>I set up a route that would allow any login, or static file to be served without\nauthentication.<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">    <\/span><span class=\"k\">location<\/span><span class=\"w\"> <\/span><span class=\"p\">~<\/span><span class=\"sr\">*<\/span><span class=\"w\"> <\/span><span class=\"s\">^\/(login|css|fonts|img|js|404.html)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"kn\">root<\/span><span class=\"w\"> <\/span><span class=\"s\">\/home\/...\/public\/<\/span><span class=\"p\">;<\/span><span class=\"w\">         <\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<\/code><\/pre><\/div>\n\n<p>And I had the main part of the website set up to use basic auth. Even had the\n<code>401<\/code> redirect in there to manage spitting users out in the right spot. But that\nused the annoying \"basic auth popup\" that I was trying to avoid.<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">    <\/span><span class=\"k\">location<\/span><span class=\"w\"> <\/span><span class=\"s\">\/<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"kn\">root<\/span><span class=\"w\"> <\/span><span class=\"s\">\/home\/...\/public\/<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">        <\/span><span class=\"kn\">auth_basic<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;Restricted&quot;<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">        <\/span><span class=\"kn\">auth_basic_user_file<\/span><span class=\"w\"> <\/span><span class=\"s\">\/home\/...\/.htpasswd<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">        <\/span><span class=\"kn\">proxy_intercept_errors<\/span><span class=\"w\"> <\/span><span class=\"no\">on<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">        <\/span><span class=\"kn\">error_page<\/span><span class=\"w\"> <\/span><span class=\"mi\">401<\/span><span class=\"w\">  <\/span><span class=\"s\">\/login\/<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<\/code><\/pre><\/div>\n\n<blockquote>\n<p>What to do, what to do...<\/p>\n<\/blockquote>\n<p>After I don't even know how much searching, it dawned on me that I could use an\n<code>if<\/code> statement to do a redirect, and after a little more tweaking, and learning\nhow to \"hack\" together a logical \"AND\" between three <code>if<\/code> statements, I was able\nto come up with this:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">    <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"s\">(<\/span><span class=\"nv\">$http_authorization<\/span><span class=\"w\"> <\/span><span class=\"p\">=<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;&quot;)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"kn\">set<\/span><span class=\"w\"> <\/span><span class=\"nv\">$temp_cache<\/span><span class=\"w\"> <\/span><span class=\"mi\">1<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"s\">(<\/span><span class=\"nv\">$request_uri<\/span><span class=\"w\"> <\/span><span class=\"s\">!~*<\/span><span class=\"w\"> <\/span><span class=\"s\">^\/(login|css|fonts|img|js|404.html))<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"kn\">set<\/span><span class=\"w\"> <\/span><span class=\"nv\">$temp_cache<\/span><span class=\"w\"> <\/span><span class=\"mi\">1<\/span><span class=\"nv\">$temp_cache<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"s\">(<\/span><span class=\"nv\">$temp_cache<\/span><span class=\"w\"> <\/span><span class=\"p\">=<\/span><span class=\"w\"> <\/span><span class=\"mi\">11<\/span><span class=\"s\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"kn\">return<\/span><span class=\"w\"> <\/span><span class=\"mi\">302<\/span><span class=\"w\"> <\/span><span class=\"s\">https:\/\/<\/span><span class=\"nv\">$host\/login\/<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<\/code><\/pre><\/div>\n\n<p>If there's no authorization header and the request URI isn't the login or static\nfile, then set a redirect to the <code>\/login<\/code> page. TADA!<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"k\">server<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"kn\">listen<\/span><span class=\"w\"> <\/span><span class=\"mi\">443<\/span><span class=\"w\"> <\/span><span class=\"s\">ssl<\/span><span class=\"p\">;<\/span><span class=\"w\"> <\/span><span class=\"c1\"># managed by Certbot<\/span>\n<span class=\"w\">    <\/span><span class=\"kn\">server_name<\/span><span class=\"w\"> <\/span><span class=\"s\">k3b4h.idaho4h.org<\/span><span class=\"p\">;<\/span>\n\n<span class=\"w\">    <\/span><span class=\"kn\">client_max_body_size<\/span><span class=\"w\"> <\/span><span class=\"s\">512M<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">    <\/span><span class=\"kn\">client_body_timeout<\/span><span class=\"w\"> <\/span><span class=\"s\">300s<\/span><span class=\"p\">;<\/span>\n\n<span class=\"w\">    <\/span><span class=\"kn\">if<\/span><span class=\"w\"> <\/span><span class=\"s\">(<\/span><span class=\"nv\">$http_authorization<\/span><span class=\"w\"> <\/span><span class=\"p\">=<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;&quot;)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"kn\">set<\/span><span class=\"w\"> <\/span><span class=\"nv\">$temp_cache<\/span><span class=\"w\"> <\/span><span class=\"mi\">1<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">    <\/span><span class=\"kn\">if<\/span><span class=\"w\"> <\/span><span class=\"s\">(<\/span><span class=\"nv\">$request_uri<\/span><span class=\"w\"> <\/span><span class=\"s\">!~*<\/span><span class=\"w\"> <\/span><span class=\"s\">^\/(login|css|fonts|img|js|404.html))<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"kn\">set<\/span><span class=\"w\"> <\/span><span class=\"nv\">$temp_cache<\/span><span class=\"w\"> <\/span><span class=\"mi\">1<\/span><span class=\"nv\">$temp_cache<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">    <\/span><span class=\"kn\">if<\/span><span class=\"w\"> <\/span><span class=\"s\">(<\/span><span class=\"nv\">$temp_cache<\/span><span class=\"w\"> <\/span><span class=\"p\">=<\/span><span class=\"w\"> <\/span><span class=\"mi\">11<\/span><span class=\"s\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"kn\">return<\/span><span class=\"w\"> <\/span><span class=\"mi\">302<\/span><span class=\"w\"> <\/span><span class=\"s\">https:\/\/<\/span><span class=\"nv\">$host\/login\/<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n\n<span class=\"w\">    <\/span><span class=\"kn\">location<\/span><span class=\"w\"> <\/span><span class=\"s\">\/<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"kn\">root<\/span><span class=\"w\"> <\/span><span class=\"s\">\/home\/...\/public\/<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">        <\/span><span class=\"kn\">auth_basic<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;Restricted&quot;<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">        <\/span><span class=\"kn\">auth_basic_user_file<\/span><span class=\"w\"> <\/span><span class=\"s\">\/home\/...\/.htpasswd<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">        <\/span><span class=\"kn\">proxy_intercept_errors<\/span><span class=\"w\"> <\/span><span class=\"no\">on<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">        <\/span><span class=\"kn\">error_page<\/span><span class=\"w\"> <\/span><span class=\"mi\">401<\/span><span class=\"w\">  <\/span><span class=\"s\">\/login\/<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n\n<span class=\"w\">    <\/span><span class=\"kn\">location<\/span><span class=\"w\"> <\/span><span class=\"p\">~<\/span><span class=\"sr\">*<\/span><span class=\"w\"> <\/span><span class=\"s\">^\/(login|css|fonts|img|js|404.html)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"kn\">root<\/span><span class=\"w\"> <\/span><span class=\"s\">\/home\/...\/public\/<\/span><span class=\"p\">;<\/span><span class=\"w\">         <\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n\n\n<span class=\"w\">    <\/span><span class=\"kn\">ssl_certificate<\/span><span class=\"w\"> <\/span><span class=\"s\">\/etc\/...fullchain.pem<\/span><span class=\"p\">;<\/span><span class=\"w\"> <\/span><span class=\"c1\"># managed by Certbot<\/span>\n<span class=\"w\">    <\/span><span class=\"kn\">ssl_certificate_key<\/span><span class=\"w\"> <\/span><span class=\"s\">\/etc\/...privkey.pem<\/span><span class=\"p\">;<\/span><span class=\"w\"> <\/span><span class=\"c1\"># managed by Certbot<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div>\n\n<p>Now... I've yet to get some javascript cobbled together to actually set the\nauthorization header. That's next, but I <em>think<\/em> that should be manageable.<\/p>\n<blockquote>\n<p>God, I hope so.<\/p>\n<\/blockquote>\n<p>Good night!<\/p>\n<h2>Update:<\/h2>\n<p>Dangit. That won't work at all. Seems that while it does everything that I want\nin terms of the visual component, it won't let me actually set the authorization\nheader for the browser to retain it for subsequent navigation.<\/p>\n<p>Maybe there's a workaround for it, but I haven't found one yet. I'll have to\nkeep thinking\/looking.<\/p>","category":[{"@attributes":{"term":"development"}},{"@attributes":{"term":"auth"}},{"@attributes":{"term":"html"}},{"@attributes":{"term":"http"}},{"@attributes":{"term":"nginx"}},{"@attributes":{"term":"proxy"}}]},{"title":"Write a Good Framework - ONCE.","link":{"@attributes":{"href":"https:\/\/blog.stanleysolutionsnw.com\/write-framework-once.html","rel":"alternate"}},"published":"2020-09-20T10:26:00-07:00","updated":"2020-09-20T10:26:00-07:00","author":{"name":"Joe Stanley"},"id":"tag:blog.stanleysolutionsnw.com,2020-09-20:\/write-framework-once.html","summary":"<p class=\"first last\">We all want that next great application; NOW. And we KNOW that we can just hash out this great new thing. But where does that leave us the next time we want to do the same sort of thing?<\/p>\n","content":"<p>C'mon, every developer has had that epiphany moment:<\/p>\n<blockquote>\n&quot;I know, I can just write this thing now, and I'll have exactly what I need, and I'll\nnever need to touch it again; I'll never need to add more to it, and it will never\nchange.&quot;<\/blockquote>\n<p>Yeah. Right.<\/p>\n<p>We wish it worked that way, but let's be honest. Projects develop, add scope, change, and\nsometimes, they morph into something new entirely. Actually, that's often where great code\ncomes from. The best projects are the butterflies of the development world. They start as\na simple little caterpillar, but at some point along the line, they change into something\nfar more elegant and beautiful.<\/p>\n<p>That's why I'm writing this plea to developers out there in the &quot;real world.&quot; We know that\nyou could just write this little thing, but isn't it nice to get the <em>framework<\/em> right the\nfirst time so that when you need to revisit that code, it'll be so much easier. Or, better\nyet, isn't it better to prepare the project for the &quot;<em>next guy<\/em>&quot; who comes along to make\nyour framework into something incredible.<\/p>\n<p>I guess I've devolved into rambling, as usual, but my point is this: when you spend the time\nto develop the framework <em>the right way<\/em> the first time, you and all of your colleagues will\nthank you. It will make things just <strong>that much easier<\/strong>.<\/p>\n<p>I've had a handful of experiences with this myself. I started on a couple projects at work\nwhere I developed something that I <em>thought<\/em> was a great system, and didn't really require\na superior framework. This inevitably saved time in the short-term, but it bit me later.<\/p>\n<p>As it turned out, I recently realized that I was re-writing the same code over, and over,\nand over again every time I had to touch the code. In the end, as it seems, I didn't spend\nenough time writing the framework to begin, and that hurt me.<\/p>\n<div class=\"section\" id=\"here-s-the-takeaway\">\n<h2>Here's the Takeaway:<\/h2>\n<p>Long story short, we all love just &quot;pulling in&quot; a library that does the &quot;dirty work&quot; for us.\nI think it's time that we focus on getting those libraries right from the onset, so we don't\nneed to keep re-doing our own work, or the work of others.<\/p>\n<p>What do you think?<\/p>\n<p>I'd say drop me a note in the comments, but I haven't quite gotten that set up yet. So, for\nnow, just keep fighting the good fight, and <em>write those libraries!<\/em><\/p>\n<\/div>\n","category":[{"@attributes":{"term":"Development"}},{"@attributes":{"term":"python"}},{"@attributes":{"term":"iec-61131"}},{"@attributes":{"term":"development"}}]}]}