<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: VNadygin</title>
    <description>The latest articles on DEV Community by VNadygin (@vnadygin).</description>
    <link>https://dev.to/vnadygin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F887567%2Fbdca75c6-0f4b-431d-94b5-93deb7d794f5.png</url>
      <title>DEV Community: VNadygin</title>
      <link>https://dev.to/vnadygin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vnadygin"/>
    <language>en</language>
    <item>
      <title>Make your links human-friendly</title>
      <dc:creator>VNadygin</dc:creator>
      <pubDate>Wed, 06 Jul 2022 11:22:19 +0000</pubDate>
      <link>https://dev.to/vnadygin/make-your-links-human-friendly-4epb</link>
      <guid>https://dev.to/vnadygin/make-your-links-human-friendly-4epb</guid>
      <description>&lt;p&gt;The reality of long URLs is supposed to be light years behind us. However, I still encounter them on almost every website. It is hard to figure out the best URL-shortener code that would also track how many times the link was opened. &lt;/p&gt;

&lt;p&gt;Using Redwood JS Framework which combines some of the most popular and advanced frameworks, I am going to create such a service.&lt;/p&gt;

&lt;p&gt;Initialise the project by following the official Get Started tutorial:&lt;br&gt;
&lt;a href="https://redwoodjs.com/docs/quick-start"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Initialize git&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first step is to add a new data table to &lt;code&gt;api/db/schema.prisma&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}

generator client {
  provider      = "prisma-client-js"
  binaryTargets = "native"
}

model WebLink {
  id           Int    @id @default(autoincrement())
  shortId      String @unique
  url          String
  viewedAmount Int    @default(0)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, the Prisma framework can help us create a SQL migration for the sqlite. I recommend using postgresql in production.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;yarn rw prisma migrate dev&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Redwood did a great job with the scaffold feature that generates a CRUD (Create, Retrieve, Update, Delete) code for React by running just one command.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;yarn rw g scaffold web-link&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Run the development mode&lt;br&gt;
&lt;code&gt;yarn redwood dev&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You will see that we have not finished the home page yet; however, four pages have already been generated:&lt;br&gt;
&lt;code&gt;/web-links/new&lt;/code&gt;&lt;br&gt;
&lt;code&gt;/web-links/{id:Int}/edit&lt;/code&gt;&lt;br&gt;
&lt;code&gt;/web-links/{id:Int}&lt;/code&gt;&lt;br&gt;
&lt;code&gt;/web-links&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0AO63ZJ8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8uoh8aejqcx4wavnqc74.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0AO63ZJ8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8uoh8aejqcx4wavnqc74.png" alt="Image description" width="880" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's open &lt;code&gt;http://localhost:8910/web-links&lt;/code&gt; and create a new data record.&lt;/p&gt;

&lt;p&gt;Now we want to add a new query to the Graphql API:&lt;br&gt;
&lt;code&gt;api/src/graphql/webLinks.sdl.js&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Add &lt;code&gt;webLinkByShortId(shortId: String!): WebLink @requireAuth&lt;/code&gt; to the SDL schema&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const schema = gql`
  type WebLink {
    id: Int!
    shortId: String!
    url: String!
    viewedAmount: Int!
  }

  type Query {
    webLinks: [WebLink!]! @requireAuth
    webLink(id: Int!): WebLink @requireAuth
+   webLinkByShortId(shortId: String!): WebLink @requireAuth
  }

  input CreateWebLinkInput {
    shortId: String!
    url: String!
    viewedAmount: Int!
  }

  input UpdateWebLinkInput {
    shortId: String
    url: String
    viewedAmount: Int
  }

  type Mutation {
    createWebLink(input: CreateWebLinkInput!): WebLink! @requireAuth
    updateWebLink(id: Int!, input: UpdateWebLinkInput!): WebLink! @requireAuth
    deleteWebLink(id: Int!): WebLink! @requireAuth
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next step is creating a function that will resolve this for us:&lt;br&gt;
open &lt;code&gt;api/src/services/webLinks/webLinks.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { db } from 'src/lib/db'

export const webLinks = () =&amp;gt; {
  return db.webLink.findMany()
}

export const webLink = ({ id }) =&amp;gt; {
  return db.webLink.findUnique({
    where: { id },
  })
}

export const createWebLink = ({ input }) =&amp;gt; {
  return db.webLink.create({
    data: input,
  })
}

export const updateWebLink = ({ id, input }) =&amp;gt; {
  return db.webLink.update({
    data: input,
    where: { id },
  })
}

export const deleteWebLink = ({ id }) =&amp;gt; {
  return db.webLink.delete({
    where: { id },
  })
}

+ export const webLinkByShortId = async ({ shortId }) =&amp;gt; {
+   await db.webLink.update({
+     where: { shortId },
+     data: { viewedAmount: { increment: 1 } },
+   })
+   return db.webLink.findUnique({ where: { shortId } })
+ }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I used the Redwood generator to generate a page.&lt;br&gt;
&lt;code&gt;yarn redwood g page Redirect&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Update &lt;code&gt;web/src/Routes.js&lt;/code&gt;. Make sure that the new route is the last on the list since it uses dynamic params.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  - &amp;lt;Route path="/redirect" page={RedirectPage} name="redirect" /&amp;gt;
  + &amp;lt;Route path="/{shortId}" page={RedirectPage} name="redirect" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we want to generate a cell. This is the way Redwood recommends working with data:&lt;br&gt;
&lt;code&gt;yarn redwood g cell redirect&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Modify &lt;code&gt;web/src/components/RedirectCell/RedirectCell.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- export const QUERY = gql`
-  query FindRedirectQuery($id: Int!) {
-    redirect: redirect(id: $id) {
-      id
-    }
-  }
- `

+ export const QUERY = gql`
+   query FindWebLink($shortId: String!) {
+     link: webLinkByShortId(shortId: $shortId) {
+       id
+       url
+     }
+   }
+ `

- export const Success = ({ redirect }) =&amp;gt; {
-   return &amp;lt;div&amp;gt;{JSON.stringify(redirect)}&amp;lt;/div&amp;gt;
- }


+ export const Success = ({ link }) =&amp;gt; {
+   React.useEffect(() =&amp;gt; {
+     if (!link) return
+
+     window.location = link.url
+   }, [link])
+
+   return 'redirecting...'
+ }


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a result, if you click on &lt;code&gt;[/kcuobL](http://localhost:8910/kcuobL)&lt;/code&gt;, the link will take you to &lt;code&gt;https://google.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8wPvFFDm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aoa89dxgx8dh6n6uvhjz.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8wPvFFDm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aoa89dxgx8dh6n6uvhjz.jpg" alt="Image description" width="880" height="87"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Voila. There is more than just a URL shortener in your hands. It is a marketing tool that can help your company's specialists to organise a huge part of their work online.  &lt;/p&gt;

</description>
    </item>
    <item>
      <title>Save your users from notification overload</title>
      <dc:creator>VNadygin</dc:creator>
      <pubDate>Wed, 06 Jul 2022 11:11:20 +0000</pubDate>
      <link>https://dev.to/vnadygin/save-your-users-from-notification-overload-30pg</link>
      <guid>https://dev.to/vnadygin/save-your-users-from-notification-overload-30pg</guid>
      <description>&lt;p&gt;Nobody wants to be spammed with notifications. Recently while working on social media app, I ran into that exact problem. Every time a user would get a follow, they would be notified with a simple: “@Username started following you”. Every single time. Of course, some users get hundreds and thousands of new followers every day, so the situation quickly became extremely annoying for them.&lt;/p&gt;

&lt;p&gt;After some thought, I decided to create a solution that would group these notifications into one. The resulting code was supposed to let users know something along the lines of  "Username1, Username2, and other 7 followed you".&lt;/p&gt;

&lt;p&gt;Managing all the moving parts of an app, such as this one, is not easy. So, we needed a scalable, long-term solution. Next came searching for a platform that would help automate the process. We needed a system that would help orchestrate the process and optimise this frequently occurring process. I stumbled across &lt;a href="https://temporal.io"&gt;Temporal.io. &lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The open source platform allows for a reliable and scalable function execution. Solutions built via Temporal are also durable, meaning there is no time limit. Whether my code was supposed to execute for seconds or years, the platform was supposed to help me make it run forever. And here’s how I did it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Here’s how developing the code for a single user follow went done:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tCIMot_v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1veyr9xl5990p1xkk4u1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tCIMot_v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1veyr9xl5990p1xkk4u1.png" alt="Image description" width="512" height="330"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Next was implementing an activity that could execute a send notification code. Workflow code cannot use 3rd party API or have access to the file system. So we need to implement it in activities and to pass a client as a dependancy injection.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Izri_JEs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tsvpr29j2v749yzi4yzi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Izri_JEs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tsvpr29j2v749yzi4yzi.png" alt="Image description" width="512" height="120"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Later I had to proxy activities in order to use them in a workflow:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hNQWHG1Z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tva9f1lulcxykksdtb6j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hNQWHG1Z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tva9f1lulcxykksdtb6j.png" alt="Image description" width="505" height="512"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A two hour period was picked for grouping notifications. Use a utility function from Temporal to make a time based condition: &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DUs30rQc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jd06ar3uwqbav4zm5yg8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DUs30rQc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jd06ar3uwqbav4zm5yg8.png" alt="Image description" width="512" height="511"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The next step includes adding a worker that will execute tasks from the queue:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4yofEaNG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aipubvjzbfhu74cftda8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4yofEaNG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aipubvjzbfhu74cftda8.png" alt="Image description" width="512" height="437"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And here it is. We are ready to run the Temporal workflow.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--T5KRz1i8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5z6hfjofpmkxxhjz4yxr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--T5KRz1i8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5z6hfjofpmkxxhjz4yxr.png" alt="Image description" width="512" height="360"&gt;&lt;/a&gt; &lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
