<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Dream Devourer]]></title><description><![CDATA[Thoughts, stories and ideas.]]></description><link>https://dreamdevourer.com/</link><image><url>http://dreamdevourer.com/favicon.png</url><title>Dream Devourer</title><link>https://dreamdevourer.com/</link></image><generator>Ghost 3.35</generator><lastBuildDate>Thu, 15 May 2025 00:03:31 GMT</lastBuildDate><atom:link href="https://dreamdevourer.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Time For Change]]></title><description><![CDATA[<p>It’s been six weeks to the day since the Automattic layoffs, so it seems fitting to finally gather my thoughts about it.</p><p>I haven’t blogged publicly for quite a while, not counting the odd code snippet post. But in the last few months I’ve been itching more</p>]]></description><link>https://dreamdevourer.com/time-for-change/</link><guid isPermaLink="false">68252dde28222e000125882d</guid><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Joanna]]></dc:creator><pubDate>Wed, 14 May 2025 23:59:00 GMT</pubDate><media:content url="http://dreamdevourer.com/content/images/2025/05/20250115_100111-2.jpg" medium="image"/><content:encoded><![CDATA[<img src="http://dreamdevourer.com/content/images/2025/05/20250115_100111-2.jpg" alt="Time For Change"><p>It’s been six weeks to the day since the Automattic layoffs, so it seems fitting to finally gather my thoughts about it.</p><p>I haven’t blogged publicly for quite a while, not counting the odd code snippet post. But in the last few months I’ve been itching more and more to do so again. I was going to dip my toes gently by writing semi-public updates to share with my work colleagues, alas always leaving it at the bottom of the todo list meant I never got to the more personal / lessons learned / interesting-things-found-on-the-web kind of P2s (internal blog posts).</p><p>Well, I guess it makes this a bit of a <strong>Goodbye</strong> post, but also a <strong>Hello</strong> at the same time.</p><h2 id="farewell">Farewell</h2><p>Working on Tumblr has been one of the best work experience I've had and I do not regret skipping the alignment offers last year. It meant I got to work on a product I liked and believed in with people I respect and admire for those extra months. So with that chapter closing here’s a look back at the last three years.</p><p>When I started, I was giddy with excitement when I got my initial team placement details (👋 <strong>Core Web</strong>). The learning curve was steep, but worth it. Being surrounded by so many knowledgeable engineers, who share your passion to learn and co-learn with others, who are quirky, and fun, and kind, was exhilarating. There was passion for the product and our users, with fierce internal debates, even if sometimes that was not visible outside (the steamroller of capitalism and 'bills need to be paid' reality getting in the way).</p><p>I gained a real appreciation for user research while on the <strong>Labs Team</strong>. I still wish we could have explored more of the weird and wonderful ideas that came from the user needs inspired brainstorming sessions.</p><p>The re-orgs were tough, but despite the impact of the company-wide changes the newly formed <strong>Tumblr Web</strong> team managed to keep the momentum going. I’m glad to have stickers with our super adorable team logo! The design system work was something we all are proud of. It probably won't be a surprise to those I've worked with, that the Storybook work we did was one of my favourite things.</p><p>The team meetups really were the secret sauce to remote work - maybe I got lucky, but after meeting my colleagues in person they quickly became more like good old friends (even though most were younger *<em>cough</em>*). I'm grateful for the travel opportunities (and all the glorious food 😂). I'll always think fondly of both the project work we did during the meetups and the goofing off after hours. I don't like naming names, but I've enjoyed working with every single person on all those teams and a bunch of others outside them. :)</p><p>Guess-the-meetup activity:</p><figure class="kg-card kg-gallery-card kg-width-wide"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="http://dreamdevourer.com/content/images/2025/05/20221213_215259-1.jpg" width="3000" height="4000" alt="Time For Change" srcset="http://dreamdevourer.com/content/images/size/w600/2025/05/20221213_215259-1.jpg 600w, http://dreamdevourer.com/content/images/size/w1000/2025/05/20221213_215259-1.jpg 1000w, http://dreamdevourer.com/content/images/size/w1600/2025/05/20221213_215259-1.jpg 1600w, http://dreamdevourer.com/content/images/size/w2400/2025/05/20221213_215259-1.jpg 2400w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="http://dreamdevourer.com/content/images/2025/05/20230511_175617-1.jpg" width="3000" height="4000" alt="Time For Change" srcset="http://dreamdevourer.com/content/images/size/w600/2025/05/20230511_175617-1.jpg 600w, http://dreamdevourer.com/content/images/size/w1000/2025/05/20230511_175617-1.jpg 1000w, http://dreamdevourer.com/content/images/size/w1600/2025/05/20230511_175617-1.jpg 1600w, http://dreamdevourer.com/content/images/size/w2400/2025/05/20230511_175617-1.jpg 2400w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="http://dreamdevourer.com/content/images/2025/05/20230714_125141-1.jpg" width="3000" height="4000" alt="Time For Change" srcset="http://dreamdevourer.com/content/images/size/w600/2025/05/20230714_125141-1.jpg 600w, http://dreamdevourer.com/content/images/size/w1000/2025/05/20230714_125141-1.jpg 1000w, http://dreamdevourer.com/content/images/size/w1600/2025/05/20230714_125141-1.jpg 1600w, http://dreamdevourer.com/content/images/size/w2400/2025/05/20230714_125141-1.jpg 2400w" sizes="(min-width: 720px) 720px"></div></div><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="http://dreamdevourer.com/content/images/2025/05/20240305_104928-1.jpg" width="3000" height="4000" alt="Time For Change" srcset="http://dreamdevourer.com/content/images/size/w600/2025/05/20240305_104928-1.jpg 600w, http://dreamdevourer.com/content/images/size/w1000/2025/05/20240305_104928-1.jpg 1000w, http://dreamdevourer.com/content/images/size/w1600/2025/05/20240305_104928-1.jpg 1600w, http://dreamdevourer.com/content/images/size/w2400/2025/05/20240305_104928-1.jpg 2400w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="http://dreamdevourer.com/content/images/2025/05/20240415_122608-1.jpg" width="3000" height="4000" alt="Time For Change" srcset="http://dreamdevourer.com/content/images/size/w600/2025/05/20240415_122608-1.jpg 600w, http://dreamdevourer.com/content/images/size/w1000/2025/05/20240415_122608-1.jpg 1000w, http://dreamdevourer.com/content/images/size/w1600/2025/05/20240415_122608-1.jpg 1600w, http://dreamdevourer.com/content/images/size/w2400/2025/05/20240415_122608-1.jpg 2400w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="http://dreamdevourer.com/content/images/2025/05/20250115_100111-1.jpg" width="3000" height="4000" alt="Time For Change" srcset="http://dreamdevourer.com/content/images/size/w600/2025/05/20250115_100111-1.jpg 600w, http://dreamdevourer.com/content/images/size/w1000/2025/05/20250115_100111-1.jpg 1000w, http://dreamdevourer.com/content/images/size/w1600/2025/05/20250115_100111-1.jpg 1600w, http://dreamdevourer.com/content/images/size/w2400/2025/05/20250115_100111-1.jpg 2400w" sizes="(min-width: 720px) 720px"></div></div></div></figure><h2 id="processing">Processing</h2><p>With all that said, I was surprised how calmly I took the layoff. The abrupt severance of access to not just work, but also: hobby discussions, parenting talk, draft knowledge sharing posts, tons of to-read-later posts saved on Slack... was eerie to say the least. In many ways it wasn't just work, the official Slack was full of smaller more personal communities, that you suddenly are booted from too. It was a new experience for me (I was the one to move on in all the positions before) and so I was glad for the friendships that allowed me to reconnect with others outside of the official Slack.</p><p>Personally the timing was odd. I had a big family trip planned just days after, followed by Easter holidays. In hindsight, this might have helped with the transition, as I was winding down things already for the two week break. But it did make for an awkward vacation juggling all the paperwork while herding kids between the pool and holiday clubs. It also meant there wasn't much point looking at the job market properly until after the break.</p><p>I've clearly had a few more weeks after that to think. Well, almost. As I said, the timing was odd. While my husband and I normally don't do big celebrations for our anniversary this year was a big, round one, so I did have a bunch of planning and herding of guests to do. So between many party checklists and food shops, and reading about my colleagues' interview prep work 🙈 I kind of, mostly, swept the big question under the metaphorical carpet.</p><h2 id="hello">Hello</h2><p>What is next then? After some soul searching (and a bunch of productivity research *<em>cough*</em> procrastination), I've come to the conclusion that while I'm open to a job that would be challenging and interesting and maybe returning to contracting, I want to first try something else. There’s been a few ideas I’ve been itching to try, but have not had time to do. There’s a parenting app I’ve been meaning to do, if just for my own use, and book writing I’ve put on hold when I had kids, as well as adventures with AI.</p><p>I wanted to document my workflows and experiments at work and share my thoughts more. Guess I’ll be doing that here instead of in internal P2s. It feels a bit weird putting it all out in the open again, but also a bit exciting.</p><p>Let’s see where it goes.<br><br></p>]]></content:encoded></item><item><title><![CDATA[Typescript vs English]]></title><description><![CDATA[<p>I kept struggling to remember what the type predicate was in Typescript... turns out I just didn't really know what a 'predicate' is in the first place... <em>shakes fist</em> darn you grammar! </p><figure class="kg-card kg-image-card"><img src="http://dreamdevourer.com/content/images/2025/03/CleanShot-2025-03-14-at-09.41.46@2x.png" class="kg-image" alt="Grammar: A predicate is the part of a sentence that tells us something about the subject. Example: The cat **is sleeping**." srcset="http://dreamdevourer.com/content/images/size/w600/2025/03/CleanShot-2025-03-14-at-09.41.46@2x.png 600w, http://dreamdevourer.com/content/images/size/w1000/2025/03/CleanShot-2025-03-14-at-09.41.46@2x.png 1000w, http://dreamdevourer.com/content/images/2025/03/CleanShot-2025-03-14-at-09.41.46@2x.png 1182w" sizes="(min-width: 720px) 720px"></figure><p> </p>]]></description><link>https://dreamdevourer.com/typescript-vs-english/</link><guid isPermaLink="false">67d3fa9128222e00012587bf</guid><category><![CDATA[dev log]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Joanna]]></dc:creator><pubDate>Fri, 14 Mar 2025 09:56:43 GMT</pubDate><media:content url="http://dreamdevourer.com/content/images/2025/03/CleanShot-2025-03-14-at-09.41.46@2x-1.png" medium="image"/><content:encoded><![CDATA[<img src="http://dreamdevourer.com/content/images/2025/03/CleanShot-2025-03-14-at-09.41.46@2x-1.png" alt="Typescript vs English"><p>I kept struggling to remember what the type predicate was in Typescript... turns out I just didn't really know what a 'predicate' is in the first place... <em>shakes fist</em> darn you grammar! </p><figure class="kg-card kg-image-card"><img src="http://dreamdevourer.com/content/images/2025/03/CleanShot-2025-03-14-at-09.41.46@2x.png" class="kg-image" alt="Typescript vs English" srcset="http://dreamdevourer.com/content/images/size/w600/2025/03/CleanShot-2025-03-14-at-09.41.46@2x.png 600w, http://dreamdevourer.com/content/images/size/w1000/2025/03/CleanShot-2025-03-14-at-09.41.46@2x.png 1000w, http://dreamdevourer.com/content/images/2025/03/CleanShot-2025-03-14-at-09.41.46@2x.png 1182w" sizes="(min-width: 720px) 720px"></figure><p> </p>]]></content:encoded></item><item><title><![CDATA[Cross Subdomain Authentication with FeathersJS Client]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>FeathersJS v4 switched from storing the jwt token in a cookie to localStorage. This was fine when the app stayed on one domain, but when splitting things out into several apps on different subdomains I realised unlike cookies localStorage does not play nicely.</p>
<p>I found the <a href="https://docs.feathersjs.com/api/authentication/client.html#customization">Customization</a> option in the</p>]]></description><link>https://dreamdevourer.com/cross-subdomain-authentication-with-feathersjs-client/</link><guid isPermaLink="false">5f8d82877a7f720001e9707b</guid><category><![CDATA[Developer]]></category><category><![CDATA[code]]></category><dc:creator><![CDATA[Joanna]]></dc:creator><pubDate>Tue, 14 Jul 2020 23:57:50 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>FeathersJS v4 switched from storing the jwt token in a cookie to localStorage. This was fine when the app stayed on one domain, but when splitting things out into several apps on different subdomains I realised unlike cookies localStorage does not play nicely.</p>
<p>I found the <a href="https://docs.feathersjs.com/api/authentication/client.html#customization">Customization</a> option in the documentation which looked promising, but for some reason using the example gave me:</p>
<pre><code>TypeError: Class constructor AuthenticationClient cannot be invoked without 'new'
</code></pre>
<p>Not wanting to go down the rabbit hole of config issues with yarn workspaces + Create React App a bit of a dirty workaround:</p>
<pre><code class="language-js">import auth from &quot;@feathersjs/authentication-client&quot;;

const MyAuthenticationClient = auth.AuthenticationClient;

//  Keep the original method as we will need to call it
MyAuthenticationClient.prototype.parentGetFromLocation =
    MyAuthenticationClient.prototype.getFromLocation;

MyAuthenticationClient.prototype.getFromLocation = function (location) {
    //  getCookie() - function to retrieve the jwt content from the cookie
    location.hash = &quot;access_token=&quot; + getCookie(&quot;MY_COOKIE_NAME&quot;);
    return this.parentGetFromLocation(location);
};

feathersAPI.configure(
  auth({
    Authentication: MyAuthenticationClient,
    storage: localStorage,
  })
);
</code></pre>
<p>This will make the jwt token stored in the parent domain cookie available during authentication. You just need to make sure the cookie is saved during the login process.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Create React App Fix for Webpack Symlinks Without Ejecting]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>When restructuring my project and moving it to use Create React App I've ended up with an odd error for my shared jsx components (I've split the project into two separate apps but some of the styling and UI components will be the same so didn't want to duplicate code.</p>]]></description><link>https://dreamdevourer.com/create-react-app-fix-for-webpack-symlinks-without-ejecting/</link><guid isPermaLink="false">5f8d82877a7f720001e9707a</guid><category><![CDATA[code]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Joanna]]></dc:creator><pubDate>Mon, 29 Jun 2020 17:30:10 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>When restructuring my project and moving it to use Create React App I've ended up with an odd error for my shared jsx components (I've split the project into two separate apps but some of the styling and UI components will be the same so didn't want to duplicate code.)</p>
<pre><code>./_shared/components/Spinner.jsx 21:2
Module parse failed: Unexpected token (21:2)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
</code></pre>
<p>I spent some time figuring out why this file refused to work while other components used the .jsx extension and did not throw errors. Eventually looking at issues with the same error I realized it was the symlinks causing problems. I had to symlink <code>_shared</code> as CRA does not allow modules outside src...</p>
<p>Now for the solution. Webpack has a config option <code>webpack.resolve.symlinks</code> that fixes the issue above.</p>
<p>But with a CRA you can't edit webpack config directly. I didn't want to eject - since the whole point of moving to CRA was to not have to deal with config discrepancies (or at least deal with them less). I already use <a href="https://github.com/gsoft-inc/craco">craco</a> for handling less and <a href="https://ant.design">antd</a>, so only needed to add the webpack section as below.</p>
<pre><code>/* craco.config.js */
module.exports = {
	// ...
	webpack: {
		configure: {
			resolve: {
				symlinks: false,
			},
		},
	},
};
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[FeathersJS Sequelize Soft Delete]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>It might have been the late hour but spent over 1.5h chasing this the other day... and getting tangled up in config and <a href="https://feathers-plus.github.io/v1/feathers-hooks-common/#softdelete2">softdelete2 feathers-plus hooks</a>.</p>
<p>Turns out to setup soft deletes in FeathersJS using feathers-sequelize you only need the <a href="https://sequelize.org/master/manual/instances.html#destroying---deleting-persistent-instances">Sequelize built in <em>paranoid</em> option</a>.</p>
<p><a href="https://stackoverflow.com/a/54688296/94814">Here's the example that</a></p>]]></description><link>https://dreamdevourer.com/feathersjs-sequelize-soft-delete/</link><guid isPermaLink="false">5f8d82877a7f720001e97079</guid><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Joanna]]></dc:creator><pubDate>Sun, 06 Oct 2019 23:13:04 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>It might have been the late hour but spent over 1.5h chasing this the other day... and getting tangled up in config and <a href="https://feathers-plus.github.io/v1/feathers-hooks-common/#softdelete2">softdelete2 feathers-plus hooks</a>.</p>
<p>Turns out to setup soft deletes in FeathersJS using feathers-sequelize you only need the <a href="https://sequelize.org/master/manual/instances.html#destroying---deleting-persistent-instances">Sequelize built in <em>paranoid</em> option</a>.</p>
<p><a href="https://stackoverflow.com/a/54688296/94814">Here's the example that makes it clear.</a></p>
<pre><code class="language-nodejs">sequelizeClient.define('exampleTable', {
	id: {
		type: DataTypes.BIGINT(20).UNSIGNED,
		primaryKey: true,
		autoIncrement: true,
	},
	...
}, {
	tableName: 'example_table',
	paranoid: true,
	deletedAt: 'deleted_at'
});
</code></pre>
<p>In essence:</p>
<ul>
<li>no need to define a field for deleted_at</li>
<li>add the <strong>paranoid: true</strong> option to your sequelize model</li>
<li>add the field name <strong>deletedAt: 'deleted_at'</strong> option to your sequelize model (if using alt name)</li>
<li>sequelize model option <strong>timestamps: true</strong> should be set, though it is the default, so you likely won't need to declare it</li>
</ul>
<p>If you need a query that includes the soft deleted items set <strong>paranoid: false</strong> in a hook.</p>
<pre><code class="language-nodejs">context =&gt; {
	context.params.sequelize = {paranoid: false};
}
</code></pre>
<p>Any <strong>includes</strong> will need their paranoid flag set:</p>
<pre><code class="language-nodejs">context =&gt; {
	context.params.sequelize = {
		include: [{
			model: SomeModel,
			paranoid: false
		}], 
		paranoid: false
	};
}
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[SSH Broken Pipe When Running in VMWare Workstation]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Another note to self post... since this bit me more than once.</p>
<pre><code>ssh USER@SERVER
...
packet_write_wait: Connection to IP_ADDRESS port 22: Broken pipe
</code></pre>
<p>Most of the answers point to the <strong>ServerAliveInterval</strong> or <strong>ClientAliveInterval</strong> but if you are running the command from inside VMWare Workstation (broken for me</p>]]></description><link>https://dreamdevourer.com/ssh-broken-pipe-when-running-in-vmware-workstation/</link><guid isPermaLink="false">5f8d82877a7f720001e97076</guid><category><![CDATA[fix]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Joanna]]></dc:creator><pubDate>Sat, 10 Nov 2018 12:18:11 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Another note to self post... since this bit me more than once.</p>
<pre><code>ssh USER@SERVER
...
packet_write_wait: Connection to IP_ADDRESS port 22: Broken pipe
</code></pre>
<p>Most of the answers point to the <strong>ServerAliveInterval</strong> or <strong>ClientAliveInterval</strong> but if you are running the command from inside VMWare Workstation (broken for me in v14 and 15.0.0) chances are the setting you want is actually <strong>IPQoS</strong>:</p>
<pre><code># ~/.ssh/config
...
Host *
IPQoS lowdelay
...
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Git Gui Startup Error on Fedora]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Recently started getting the following error when trying to run <strong>git gui</strong>. Googling the issue didn't seem to help much.</p>
<pre><code>Error in startup script: bad pad value &quot;2m&quot;: must be positive screen distance
    while executing
&quot;grid $w.bitmap $w.msg -in $w.top -sticky news -padx 2m</code></pre>]]></description><link>https://dreamdevourer.com/git-gui-startup-error-on-fedora/</link><guid isPermaLink="false">5f8d82877a7f720001e97075</guid><category><![CDATA[Developer]]></category><category><![CDATA[linux]]></category><dc:creator><![CDATA[Joanna]]></dc:creator><pubDate>Sat, 26 May 2018 20:25:02 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Recently started getting the following error when trying to run <strong>git gui</strong>. Googling the issue didn't seem to help much.</p>
<pre><code>Error in startup script: bad pad value &quot;2m&quot;: must be positive screen distance
    while executing
&quot;grid $w.bitmap $w.msg -in $w.top -sticky news -padx 2m -pady 2m&quot;
    (procedure &quot;::tk::MessageBox&quot; line 181)
    invoked from within
&quot;::tk::MessageBox {*}$args&quot;
    (procedure &quot;tk_messageBox&quot; line 2)
    invoked from within
&quot;tk_messageBox -icon error -type ok -title {Git Gui (dev): error} -message {Spell checking is unavailable:

No word lists can be found for the language...&quot;
    (&quot;eval&quot; body line 1)
    invoked from within
&quot;eval $cmd&quot;
    (procedure &quot;error_popup&quot; line 10)
    invoked from within
&quot;error_popup [strcat [mc &quot;Spell checking is unavailable&quot;] &quot;:\n\n$err&quot;]&quot;
    (procedure &quot;_connect&quot; line 28)
    invoked from within
&quot;_connect $this $pipe_fd&quot;
    (procedure &quot;spellcheck::init&quot; line 7)
    invoked from within
&quot;spellcheck::init  $spell_fd  $ui_comm  $ui_comm_ctxm  &quot;
    invoked from within
&quot;if {[winfo exists $ui_comm]} {
	set GITGUI_BCK_exists [load_message GITGUI_BCK utf-8]

	# -- If both our backup and message files exist use the
	#    ...&quot;
    (file &quot;/usr/libexec/git-core/git-gui&quot; line 3954)
</code></pre>
<p>At first I thought it's a tcl/tk issue, as related things kept popping up. However then I remembered I was having some dictionary issues not long ago that resulted in an annoying error pop-up from git gui (but it would still let me use the tool after dismissing the popup...).</p>
<p>I did find a fix for that one, but didn't get round to applying it on my home machine before a system update. And then the above happened.</p>
<p>It was only after reading through the whole message I noticed this line:</p>
<p><em>No word lists can be found for the language...</em></p>
<p>Oh... Could this really be the same issue?</p>
<p>Yep. And the fix is quite simple:</p>
<p><code>dnf install aspell-en</code> <em>or the language of your choice</em></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Example of Sequelize Associations in FeathersJS]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>I've been playing around with <a href="http://dreamdevourer.com/example-of-sequelize-associations-in-feathersjs/feathersjs.com">FeathersJS</a> and one thing that has been hard to find is a concrete example of setting up <a href="http://docs.sequelizejs.com/">Sequelize</a> with related tables.</p>
<p>Here's an example of the <strong>users</strong> table referencing a  <strong>user_statuses</strong> table using the new <code>model.associate()</code> syntax including <em>populating</em> the output of the</p>]]></description><link>https://dreamdevourer.com/example-of-sequelize-associations-in-feathersjs/</link><guid isPermaLink="false">5f8d82877a7f720001e97074</guid><category><![CDATA[code]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Joanna]]></dc:creator><pubDate>Thu, 08 Mar 2018 22:22:56 GMT</pubDate><media:content url="http://dreamdevourer.com/content/images/2017/01/post-cover-code-1.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="http://dreamdevourer.com/content/images/2017/01/post-cover-code-1.jpg" alt="Example of Sequelize Associations in FeathersJS"><p>I've been playing around with <a href="http://dreamdevourer.com/example-of-sequelize-associations-in-feathersjs/feathersjs.com">FeathersJS</a> and one thing that has been hard to find is a concrete example of setting up <a href="http://docs.sequelizejs.com/">Sequelize</a> with related tables.</p>
<p>Here's an example of the <strong>users</strong> table referencing a  <strong>user_statuses</strong> table using the new <code>model.associate()</code> syntax including <em>populating</em> the output of the users.find() endpoint with a hook.</p>
<h2 id="modelsetup">Model setup</h2>
<p>The user's model here is the basic file generated by <a href="https://docs.feathersjs.com/api/authentication/server.html">@feathersjs/authentication</a> with just the foreign key added and the relationship defined.</p>
<pre><code># src/models/user.model.js
# file generated using @feathers/cli

const Sequelize = require('sequelize');
const DataTypes = Sequelize.DataTypes;

module.exports = function(app){
	const sequelizeClient = app.get('sequelizeClient');
	const users = sequelizeClient.define('users', {
		email: {
			type: DataTypes.STRING,
			allowNull: false,
			unique: true
		},
		password: {
			type: DataTypes.STRING,
			allowNull: false
		},
		statusId: {
			type: Sequelize.INTEGER,
			field: 'status_id'
		}

	}, {
		hooks: {
			beforeCount(options){
				options.raw = true;
			}
		},
	});

	users.associate = function(models){
		users.hasOne(models.userStatuses, {
			as: 'UserStatus',
			foreignKey: 'id'
		});
	};

	return users;
};

</code></pre>
<p>UserStatuses model - just the generated file. No changes really needed for this one way relationship.</p>
<pre><code># src/models/user-statuses.model.js
# file generated using @feathers/cli

const Sequelize = require('sequelize');
const DataTypes = Sequelize.DataTypes;

module.exports = function(app){
	const sequelizeClient = app.get('sequelizeClient');
	const userStatuses = sequelizeClient.define('userStatuses', {
		name: {
			type: DataTypes.STRING
		}
	}, {
		hooks: {
			beforeCount(options){
				options.raw = true;
			}
		},
	});

	userStatuses.associate = function(models){
	};

	return userStatuses;
};

</code></pre>
<h2 id="populatingtherelationship">Populating the Relationship</h2>
<p>The goal is to return the related table as part of a users.find() call. As a bare minimum you want to set two Sequelize params in a before hook: include (see <a href="http://docs.sequelizejs.com/manual/tutorial/associations.html">Sequelize documentation</a> for more details on this option) and raw:true (if you want a nested object rather than a flat structure).</p>
<pre><code># src/services/users/users.hooks.js
# file generated by @feathers/authentication

const {authenticate} = require('@feathersjs/authentication').hooks;
const {
	hashPassword, protect
} = require('@feathersjs/authentication-local').hooks;

module.exports = {
	before: {
		all: [],
		find: [
                        authenticate('jwt'),
                        //
                        //      Quick &amp; dirty example 
                        //
                        context =&gt; {
                                const sequelize = context.params.sequelize || {};
                                sequelize.raw = true;
                                sequelize.include = [
		        		{
			        		model: context.app.services['user-statuses'].Model,
				        	as: 'UserStatus'
				        }
			        ];
                                return context;
	        	},
                ],
		get: [authenticate('jwt')],
		create: [hashPassword()],
		update: [hashPassword(), authenticate('jwt')],
		patch: [hashPassword(), authenticate('jwt')],
		remove: [authenticate('jwt')]
	},

	after: {
		all: [
			// Make sure the password field is never sent to the client
			// Always must be the last hook
			protect('password')
		],
		find: [],
		get: [],
		create: [],
		update: [],
		patch: [],
		remove: []
	},

	error: {
		all: [],
		find: [],
		get: [],
		create: [],
		update: [],
		patch: [],
		remove: []
	}
};

</code></pre>
<p>As various models might make use of this functionality I've actually created a generic hook <strong>addAssociations()</strong>. It takes multiple models for the include option and looks up the model object for you based on a shorthand string. <em>(I'm still looking at a good way of handling hyphens vs camelcase so excuse the mix of names in the example below)</em></p>
<pre><code># src/hooks/add-associations.js
# hook generated by @feathers/cli

module.exports = function(options = {}){
	options.models = options.models || [];

	return async context =&gt;{
		const sequelize = context.params.sequelize || {};
		const include = sequelize.include || [];

		//	Reasign in case we created these properties
		sequelize.include = include.concat(options.models.map(model =&gt; {
			const newModel = {...model};

			newModel.model = context.app.services[model.model].Model;
			return newModel;
		}));

		//	Nested output
		sequelize.raw = false;

		context.params.sequelize = sequelize;
		return context;
	};
};

</code></pre>
<p>And here's the updated <strong>users.hooks.js</strong> file:</p>
<pre><code># src/services/users/users.hooks.js
# file generated by @feathers/authentication

const {authenticate} = require('@feathersjs/authentication').hooks;
const addAssociations = require('./../../hooks/add-associations');

const {
	hashPassword, protect
} = require('@feathersjs/authentication-local').hooks;

module.exports = {
	before: {
		all: [],
		find: [
			authenticate('jwt'),
			addAssociations({
				models: [
					{
						model: 'user-statuses',
						as: 'UserStatus'
					}
				]
			})
		],
		get: [authenticate('jwt')],
		create: [hashPassword()],
		update: [hashPassword(), authenticate('jwt')],
		patch: [hashPassword(), authenticate('jwt')],
		remove: [authenticate('jwt')]
	},

	after: {
		all: [
			// Make sure the password field is never sent to the client
			// Always must be the last hook
			protect('password')
		],
		find: [],
		get: [],
		create: [],
		update: [],
		patch: [],
		remove: []
	},

	error: {
		all: [],
		find: [],
		get: [],
		create: [],
		update: [],
		patch: [],
		remove: []
	}
};
</code></pre>
<p>All this results in a nice nested JSON response:</p>
<pre><code>{
    &quot;total&quot;: 1,
    &quot;limit&quot;: 10,
    &quot;skip&quot;: 0,
    &quot;data&quot;: [
        {
            &quot;id&quot;: 1,
            &quot;email&quot;: &quot;name@example.com&quot;,
            &quot;statusId&quot;: 1,
            &quot;created_at&quot;: &quot;2017-11-08T15:25:01.000Z&quot;,
            &quot;updated_at&quot;: &quot;2017-11-08T15:27:29.000Z&quot;,
            &quot;UserStatus&quot;: {
                &quot;id&quot;: 1,
                &quot;name&quot;: &quot;active&quot;,
                &quot;created_at&quot;: &quot;2017-11-08T15:25:01.000Z&quot;,
                &quot;updated_at&quot;: &quot;2017-11-08T15:25:01.000Z&quot;
            }
        }
    ]
}
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Easy Way to Migrate Wordpress Sites Between Servers]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>As part of cleanup and cost-cutting I had to recently moved my WordPress installations from one server to another. To make matters a bit more complicated the new server was not a cPanel based one. So, I had to move my WordPress instances, but not really the user accounts that</p>]]></description><link>https://dreamdevourer.com/easy-way-to-migrate-wordpress-sites-between-servers/</link><guid isPermaLink="false">5f8d82877a7f720001e9703c</guid><category><![CDATA[wordpress]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Joanna]]></dc:creator><pubDate>Fri, 20 Jan 2017 18:51:20 GMT</pubDate><media:content url="http://dreamdevourer.com/content/images/2017/01/post-cover-updraft.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="http://dreamdevourer.com/content/images/2017/01/post-cover-updraft.jpg" alt="Easy Way to Migrate Wordpress Sites Between Servers"><p>As part of cleanup and cost-cutting I had to recently moved my WordPress installations from one server to another. To make matters a bit more complicated the new server was not a cPanel based one. So, I had to move my WordPress instances, but not really the user accounts that held them as such.</p>
<p>After getting a bit frustrated with WordPress MU and going through several plug-ins for static sites (neither of which really worked…), I finally found something that did.</p>
<p>Enter: <a href="https://updraftplus.com/">UpdraftPlus - Backup/Restore plug-in</a></p>
<p>The free version was enough to get the job I needed done. It’s quite a robust plugin that functions as a backup, but also works really well for migrating sites to a new server while maintaining the same domain name.</p>
<p>With docker on the other end the process ended up being quite simple:</p>
<ol>
<li>Install Updraft on the old WordPress instance</li>
<li>Run the plugin and backup everything</li>
<li>Create a new docker instance (I've used <a href="https://hub.docker.com/_/wordpress/">the official Wordperss image</a>)</li>
<li>Use /etc/hosts to access the new WordPress install under the old domain</li>
<li>Setup the new WordPress instance - doesn't matter really what details you use, as we'll override everything soon enough</li>
<li>Install the Updraft plugin</li>
<li>Upload the backup files downloaded earlier</li>
<li>Press restore and enjoy your new site!</li>
</ol>
<p>So much easier than trying to deal with the native export tool WordPress provides that unfortunately doesn’t cover plugin or theme settings.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Assassin's Creed Movie]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>I should probably start by admitting that I haven't played the game itself. But like must gamers (unless they've been living under a rock) I had an idea about what Assassin's Creed is about. I knew about the praise it got and about the interesting &quot;time travel&quot; bit,</p>]]></description><link>https://dreamdevourer.com/assassins-creed-movie/</link><guid isPermaLink="false">5f8d82877a7f720001e97039</guid><category><![CDATA[movies]]></category><category><![CDATA[Life]]></category><dc:creator><![CDATA[Joanna]]></dc:creator><pubDate>Mon, 09 Jan 2017 01:28:34 GMT</pubDate><media:content url="http://dreamdevourer.com/content/images/2017/01/post-cover-assassins.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="http://dreamdevourer.com/content/images/2017/01/post-cover-assassins.jpg" alt="Assassin's Creed Movie"><p>I should probably start by admitting that I haven't played the game itself. But like must gamers (unless they've been living under a rock) I had an idea about what Assassin's Creed is about. I knew about the praise it got and about the interesting &quot;time travel&quot; bit, However it being a bit too action oriented for my taste one never actually played it. The premise intrigued me, but there always were enough actual rpgs to keep me occupied.</p>
<p>Cue the movie. Right from the trailers it looked great. And after seeing it, Assassin's Creed the movie is definitely worth the big screen. It unapologetically mimics game aesthetics and maybe it says a lot about games too, but some scenes felt exactly like something that would be part of gameplay.</p>
<p>I haven't read any reviews (I find too many film critics are just cinema snobs who don't understand the movie genre nor the target audience if the movie isn't some sort of deep drama...) beforehand as I like to go in unbiased. It's often hard to stop thinking about something that a person mentioned as a flaw and that then ruins my enjoyment.</p>
<p>The movie was <strong>cool</strong>. Not groundbreaking, not complex, not full of Oscar winning performances. But it was a fun romp with great score over dynamic chases and some great parkour sequences. I enjoyed the special effects, and so what that there were plenty of plot holes. Sometimes you just go with it and enjoy the ride.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[How to Remember Tar Parameters]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>I've got the extraction options for <strong>tgz</strong> files pretty well ingrained in my brain, but whenever I had to create an archive I always had to go back to the manual.</p>
<p>Not any more! Annoyed at my terrible memory I created a little mnemonic for myself. Here's the command in</p>]]></description><link>https://dreamdevourer.com/how-to-remember-tar-parameters/</link><guid isPermaLink="false">5f8d82877a7f720001e97037</guid><category><![CDATA[Developer]]></category><category><![CDATA[linux]]></category><category><![CDATA[study-skills]]></category><dc:creator><![CDATA[Joanna]]></dc:creator><pubDate>Thu, 05 Jan 2017 21:41:07 GMT</pubDate><media:content url="http://dreamdevourer.com/content/images/2017/01/post-cover-code2.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="http://dreamdevourer.com/content/images/2017/01/post-cover-code2.jpg" alt="How to Remember Tar Parameters"><p>I've got the extraction options for <strong>tgz</strong> files pretty well ingrained in my brain, but whenever I had to create an archive I always had to go back to the manual.</p>
<p>Not any more! Annoyed at my terrible memory I created a little mnemonic for myself. Here's the command in question:</p>
<p><code>tar -czvf FILENAME.tgz FOLDER</code></p>
<p>And to remember the params I've got this nifty sentence:</p>
<p><strong>Tar</strong> <strong>C</strong>an <strong>Z</strong>ip <strong>V</strong>ery <strong>F</strong>ast</p>
<p>I guess it should be g<strong>Z</strong>ip for accuracy, but hey it works for me ;)</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Never Say Never]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Sometimes you have to take one step back to take two forward. Or in other words, never say never. Or to put it in simple terms – I’ve gone perm again…</p>
<p>I’ve been pretty happy with the contracting work I’ve done over the last year and a bit.</p>]]></description><link>https://dreamdevourer.com/never-say-never/</link><guid isPermaLink="false">5f8d82877a7f720001e97032</guid><category><![CDATA[work]]></category><category><![CDATA[Writer]]></category><category><![CDATA[Publisher]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Joanna]]></dc:creator><pubDate>Fri, 02 Oct 2015 09:05:40 GMT</pubDate><media:content url="http://dreamdevourer.com/content/images/2017/01/castle-trust-1.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="http://dreamdevourer.com/content/images/2017/01/castle-trust-1.png" alt="Never Say Never"><p>Sometimes you have to take one step back to take two forward. Or in other words, never say never. Or to put it in simple terms – I’ve gone perm again…</p>
<p>I’ve been pretty happy with the contracting work I’ve done over the last year and a bit. After being preoccupied almost exclusively with the publishing work before that, it’s been a nice change to be coding full-time again (occasional side projects here and there don’t really count in my book). I’ve also been lucky enough to meet some great people along the way.</p>
<p>Despite being a loner at heart I had to admit I did miss the office banter a bit. So when I ended up contracting at an exciting company with a great team, the year had flown by fast. And while I was adamant I wouldn’t be going back to full time employment, sometimes the stars align just right and an opportunity lands in your lap that you just can’t refuse.</p>
 <div class="goRight">
![Castle Trust Innovative solutions for unique situations](https://www.castletrust-intermediaries.co.uk/assets/gfx/castle-trust.png)
</div>
<p>With a slew of exciting projects ahead I’m thrilled to join Castle Trust this month as their Lead Developer. Yay! :)</p>
<p><a href="http://amzn.to/1M5Ojge"><img src="http://jcharker.com/img/books/feral.jpg" alt="Never Say Never"></a></p>
<p>At the same time, this does not mean I’m saying goodbye to my publishing dreams. On the contrary – “<a href="http://amzn.to/1M5Ojge">Feral: The Dragon Within</a>” has just been release (expect a longer post soon about the 2+ years of work I put into that book), and while I’m working on Feral part 2 and Hellena’s story, there’s some exciting things I’d like to try with the Wing &amp; Fang brand soon too.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Killednloading MeteorJS Issue]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>When deploying my meteor app to a new server I ran into a weird issue that didn’t seem to come back with any helpful errors.</p>
<p>Running:</p>
<p>``</p>
<p>meteor</p>
<p>would get stuck on <strong>Download meteor-tool@3.0.38</strong> which would eventually exit with a “killednloading” message.</p>
<p>For anyone else stuck scratching</p>]]></description><link>https://dreamdevourer.com/killednloading-meteorjs-issue/</link><guid isPermaLink="false">5f8d82877a7f720001e9702f</guid><dc:creator><![CDATA[thinksentient]]></dc:creator><pubDate>Mon, 23 Feb 2015 22:34:55 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>When deploying my meteor app to a new server I ran into a weird issue that didn’t seem to come back with any helpful errors.</p>
<p>Running:</p>
<p>``</p>
<p>meteor</p>
<p>would get stuck on <strong>Download meteor-tool@3.0.38</strong> which would eventually exit with a “killednloading” message.</p>
<p>For anyone else stuck scratching their heads, it seem to be an out of memory issue.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[How We Listen to the Music We Love]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>This wasn’t what I planned on writing about today… But since I’ve updated my gaming machine the other day (Goodbye Vista!  – yeah, I was surprised too, to see it was still running that…) I’ve been doing a little bit of clean up on it. The usual, tune</p>]]></description><link>https://dreamdevourer.com/how-we-listen-to-the-music-we-love/</link><guid isPermaLink="false">5f8d82877a7f720001e9702e</guid><category><![CDATA[music]]></category><category><![CDATA[musings]]></category><category><![CDATA[personal]]></category><category><![CDATA[stuff I use]]></category><dc:creator><![CDATA[jo]]></dc:creator><pubDate>Tue, 01 Apr 2014 05:40:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>This wasn’t what I planned on writing about today… But since I’ve updated my gaming machine the other day (Goodbye Vista!  – yeah, I was surprised too, to see it was still running that…) I’ve been doing a little bit of clean up on it. The usual, tune settings, update drivers, remove unused or broken programs… That’s when I spotted something I haven’t used for a very long while.</p>
<p><strong>Winamp!</strong></p>
<p>Oh, my. Now that’s a blast from the past. I felt a bit sentimental when removing it. But these days I’m addicted to <a href="http://www.spotify.com">Spotify</a>. It’s funny how things evolve. I don’t have many paid subscriptions, especially for things that aren’t related to running my business. Spotify is one of the few exceptions.</p>
<p>As a kid I started out with cassette tapes, but during my teens we quickly moved on to CDs. And then there were mp3s. I used to hoard those. Although I didn’t have a portable player for a long while. Though, funny enough I can’t quite remember what I used for on the go music in the period between tapes and an mp3 player – since I’m pretty sure I didn’t have a portable CD player… But back to my hoarding habits. Although mp3 players and then phones allowed me to take a selection of my files with me it wasn’t quite the same as having access to everything. And then I lost it all. Or almost all in a hard drive disaster. My fault really since I didn’t have a backup.</p>
<p>I tried restoring that collection and overtime grew my hoard a bit again. But before I had a chance to go on a proper shopping spree I found Spotify. At first I was a bit doubtful if it could replace “owned” music for me.  But as time went by I found, that pretty much anything I wanted to listen to was on there. Almost everything… and on that in a moment.</p>
<p>Funny enough I didn’t start paying for Spotify for it’s mobile use or to get rid of the ads. I actually enjoyed the ads. Most of them were funny, at least back when I was on free. No, what got me to pay were my bad listening habits. Yes, I’m one of those annoying people who listen to something over-and-over-and-over-and-over-and… you get the point. Playlists, sometimes an individual song. So when they introduced a play limit on the free option that was it. I had to become a subscriber.</p>
<p>So, you might wonder what  is it that I’m missing with Spotify. Well. The one thing that still isn’t there is non-English music. That’s something I lost when my hard drive died – a whole lot of Polish music. Some of it I could in theory recreate from the CDs I owned (or tapes??? As if anyone still had something that plays those…), but there were a few random indie/local bands I just had a song or two of. But in the end you just move on.  On a positive note, I’m actually seeing more and more Polish artists on Spotify too. It’s frustrating that sometimes they’ll just have one, latest album on there, when I’m looking for the classics I know.</p>
<p>I remember the awe and excitement when instead of a single album or a handful of pre-selected songs I moved to my extensive playlist on an mp3 player. I get the same awe now when I stop to think about the music I have at my fingertips through my phone. Everything available anywhere you are (OK, abroad trips can still be a challenge).</p>
<p>Just as I never though I’d move on from mp3s stored on my drive, I can’t imagine moving to some other way of consuming music over Spotify now. But maybe the successor is already out there. Anyone know of a revolution going on?</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[SailsJS 0.9.8 and Bootstrap 3.0.3 LESS]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>While trying to setup a starter project I hit a bit of a snag. Turns out the latest less files don’t want to play nice with the grunt-contrib-less (0.5.2) that comes with SailsJS (0.9.8).</p>
<pre><code>parse error: failed at &amp;:extend(.clearfix all);
</code></pre>
<p>Since the required</p>]]></description><link>https://dreamdevourer.com/sailsjs-0-9-8-and-bootstrap-3-0-3-less/</link><guid isPermaLink="false">5f8d82877a7f720001e9702c</guid><category><![CDATA[fix]]></category><category><![CDATA[grunt]]></category><category><![CDATA[less]]></category><category><![CDATA[nodejs]]></category><category><![CDATA[sailsjs]]></category><category><![CDATA[tip]]></category><dc:creator><![CDATA[thinksentient]]></dc:creator><pubDate>Thu, 23 Jan 2014 06:54:21 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>While trying to setup a starter project I hit a bit of a snag. Turns out the latest less files don’t want to play nice with the grunt-contrib-less (0.5.2) that comes with SailsJS (0.9.8).</p>
<pre><code>parse error: failed at &amp;:extend(.clearfix all);
</code></pre>
<p>Since the required version is written explicitly in SailsJS package.json it’s not enough to just update your own project dependencies to the newer grunt/less versions (duh! took me a while before I realized that).</p>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>