<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[HàPhan 河]]></title><description><![CDATA[Become a better developer]]></description><link>http://www.hapq.me:80/</link><image><url>http://www.hapq.me:80/favicon.png</url><title>HàPhan 河</title><link>http://www.hapq.me:80/</link></image><generator>Ghost 3.42</generator><lastBuildDate>Sun, 08 Mar 2026 09:43:15 GMT</lastBuildDate><atom:link href="http://www.hapq.me:80/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA["I stole a Moonstone from Google office"]]></title><description><![CDATA[<p>Tokyo, close to midnight.</p><p>Rain slicked the neon-streaked streets, painting the city in shimmering, blurred strokes of light. Inside the Google Shibuya office, a profound silence hummed, broken only by the distant thrum of the metropolis. I was an intruder, a ghost in the machine, my presence a violation of</p>]]></description><link>http://www.hapq.me:80/i-stole-a-moonstone-from-google-office-in-my-dream/</link><guid isPermaLink="false">690b35c8b256260463ccf4eb</guid><dc:creator><![CDATA[HàPQ]]></dc:creator><pubDate>Wed, 05 Nov 2025 12:00:49 GMT</pubDate><content:encoded><![CDATA[<p>Tokyo, close to midnight.</p><p>Rain slicked the neon-streaked streets, painting the city in shimmering, blurred strokes of light. Inside the Google Shibuya office, a profound silence hummed, broken only by the distant thrum of the metropolis. I was an intruder, a ghost in the machine, my presence a violation of the digital sanctuary.</p><p>For months, I’d been chasing whispers—coordinates hidden in data streams, fragments of code echoing in the vastness of machine learning algorithms, cryptic messages tucked away like digital breadcrumbs. Each clue led me to a new, seemingly insignificant object, unremarkable on its own, but together, forming a mosaic of a larger, unfolding mystery. This time, however, the stakes felt different.</p><p>The latest directive was a minimalist enigma: "Find the stone that remembers the light." It had led me to this glass and steel fortress.</p><p>I found the indoor garden on the fifth floor, a tranquil oasis designed for tech-weary minds. A pristine rectangle of fine sand, a scattering of elegant bamboo stalks, and a meticulous arrangement of smooth river stones. They all looked identical, dozens of muted gray stones, perfectly rounded, perfectly balanced, perfectly ordinary.</p><p>But as I stepped closer, a subtle shift occurred. It wasn't visual; it was a visceral pull, a quiet hum against my skin, as if one of them was calling to me. A silent gaze met mine.</p><p>I crouched, my fingers tracing the cool, smooth surface of the third stone from the edge. It felt alive, radiating a faint, almost imperceptible tremor, like distant static. Just a stone among stones, yet a flicker of something ancient and potent passed from its surface to my fingertips. Without a moment's hesitation, I slipped it into my pocket.</p><figure class="kg-card kg-image-card"><img src="http://www.hapq.me/content/images/2025/11/Gemini_Generated_Image_amt6sfamt6sfamt6.png" class="kg-image" alt srcset="http://www.hapq.me/content/images/size/w600/2025/11/Gemini_Generated_Image_amt6sfamt6sfamt6.png 600w, http://www.hapq.me/content/images/size/w1000/2025/11/Gemini_Generated_Image_amt6sfamt6sfamt6.png 1000w, http://www.hapq.me/content/images/2025/11/Gemini_Generated_Image_amt6sfamt6sfamt6.png 1024w" sizes="(min-width: 720px) 720px"></figure><p>That's when the world fractured.</p><p>A strident, digital shriek tore through the silence, followed by the pulsating wash of crimson light flooding the hallway.</p><p>A calm, almost unnervingly polite voice echoed through the intercom system: "Security alert. Please remain where you are."</p><p>I didn't.</p><p>Adrenaline surged, a fierce current propelling me through glass-walled corridors and chrome pathways. My footsteps, usually an echo in the quiet office, were swallowed by the piercing wail of the siren. The elevator was locked down, a red X glowing ominously, so I plunged into the stairwell, my heart hammering a frantic rhythm against my ribs. I half-expected to hear shouts, to feel the grip of a security guard, but there was only the siren's relentless cry.</p><p>Outside, the rain intensified, blurring the city lights into impressionistic streaks across the glistening pavement. I pulled out the stone. It looked utterly unremarkable—just like the others, no visible glow, no discernible hum. No proof of the clandestine energy that had coursed through it.</p><p>But then, my phone screen flickered to life. A single message, from an unknown sender, materialized:</p><p>"3 of 9. Memory secured. Proceed."</p><p>I stood there, the rain soaking my sleeve, the stone heavy in my palm, and for the first time, a chilling question surfaced: Am I truly collecting these fragments for myself, or am I merely a puppet, a conduit for something far older and more formidable, something that watches through my eyes?</p>]]></content:encoded></item><item><title><![CDATA[🤖 Automating Code Reviews with LangChain, GitLab MCP, and RAG]]></title><description><![CDATA[<p>Imagine this: a developer pushes code to a merge request on GitLab. Immediately, our AI agent springs into action. It reviews the changes, cross-referencing them with the existing codebase, identifying potential issues, suggesting improvements, and even explaining why certain changes might be problematic. All of this, as a comment directly</p>]]></description><link>http://www.hapq.me:80/smart-code-smarter-reviews-building-an-ai-code-review-agent-with-langchain-and-gitlab/</link><guid isPermaLink="false">6837034db256260463ccf3de</guid><dc:creator><![CDATA[HàPQ]]></dc:creator><pubDate>Wed, 28 May 2025 13:15:11 GMT</pubDate><content:encoded><![CDATA[<p>Imagine this: a developer pushes code to a merge request on GitLab. Immediately, our AI agent springs into action. It reviews the changes, cross-referencing them with the existing codebase, identifying potential issues, suggesting improvements, and even explaining why certain changes might be problematic. All of this, as a comment directly on the merge request.</p><p>Creating an AI-powered code review agent that integrates with GitLab can significantly streamline the development process. By leveraging LangChain, the Model Context Protocol (MCP), and Retrieval-Augmented Generation (RAG), we can build an agent capable of retrieving, analyzing, and providing feedback on code changes.This guide will walk you through setting up such an agent, complete with sample code snippets.</p><h2 id="-overview">🧠 Overview</h2><p>Our AI code review agent will:</p><p><strong>Connect to GitLab</strong>: Utilize an MCP server to interact with GitLab repositories.<br><strong>Index Source Code</strong>: Employ RAG to embed and index the codebase for efficient retrieval.<br><strong>Analyze Merge Requests</strong>: Automatically fetch and review merge requests, providing actionable feedback.</p><h2 id="-prerequisites">🛠️ Prerequisites</h2><p>Before we begin, ensure you have the following:</p><!--kg-card-begin: markdown--><ul>
<li>
<p>Python 3.8+- Node.js (v16+)</p>
</li>
<li>
<p>GitLab Personal Access Token</p>
</li>
<li>
<p>GitLab MCP Server: A server that exposes GitLab functionalities via MCP. Refer to <a href="https://playbooks.com/mcp/rifqi96-gitlab">GitLab MCP Server</a> for setup instructions</p>
</li>
<li>
<p>Required Python Packages</p>
</li>
</ul>
<pre><code>pip install langchain faiss-cpu openai mcp-use
</code></pre>
<!--kg-card-end: markdown--><h2 id="-step-by-step-building-the-agent">💡Step-by-Step: Building the Agent</h2><p>Let's break down the development process.</p><!--kg-card-begin: markdown--><h3 id="indexingthecodebasewithrag">🧱 Indexing the Codebase with RAG</h3>
<p>Retrieval-Augmented Generation (RAG) enhances the agent's ability to provide contextually relevant feedback by retrieving pertinent information from the codebase.</p>
<h4 id="loadandsplitthecodebase">Load and Split the Codebase:</h4>
<pre><code>from langchain_community.document_loaders.generic import GenericLoader
from langchain_community.document_loaders.parsers import LanguageParser
from langchain.text_splitter import RecursiveCharacterTextSplitter

# Load all code files from the local folder
loader = GenericLoader.from_filesystem(
    &quot;./source&quot;,
    glob=&quot;**/*&quot;,
    suffixes=[&quot;.swift&quot;],
    parser=LanguageParser()
)
documents = loader.load()

# Split documents into manageable chunks
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,      # Max characters per chunk
    chunk_overlap=200,    # Overlap between chunks to maintain context
    length_function=len,
    add_start_index=True, # Add metadata about the start index of the chunk
)
chunks = text_splitter.split_documents(documents)

</code></pre>
<h4 id="generateembeddingsandstoreinvectordatabase">Generate Embeddings and Store in Vector Database:</h4>
<pre><code>from langchain_huggingface import HuggingFaceEmbeddings
from langchain_chroma import Chroma

# Initialize the embedding model
embeddings = HuggingFaceEmbeddings(model_name=&quot;all-MiniLM-L6-v2&quot;)

# Check if the vector store already exists to create or load
persist_directory = &quot;./chroma&quot;
if os.path.exists(persist_directory) and os.listdir(persist_directory):
    print(f&quot;Loading existing Chroma DB from {persist_directory}&quot;)
    vectorstore = Chroma(persist_directory=persist_directory, embedding_function=embeddings)
else:
    # Ensure the persist directory exists
    os.makedirs(persist_directory, exist_ok=True)
    vectorstore = Chroma.from_documents(
        documents=chunks,
        embedding=embeddings,
        persist_directory=persist_directory
    )
    vectorstore.persist()
    print(&quot;Chroma DB created and persisted.&quot;)
</code></pre>
<p>This setup allows the agent to retrieve relevant code snippets when analyzing merge requests.</p>
<h4 id="definetheragretrievaltool">Define the RAG Retrieval Tool</h4>
<p>Create a custom tool that utilizes the vector store to retrieve relevant code snippets:</p>
<pre><code>from langchain.agents import Tool

def retrieve_code_snippets(query):
    # Retrieve relevant documents based on the query
    docs = vectorstore.similarity_search(query)
    # Combine the content of the documents into a single string
    return &quot;\n&quot;.join([doc.page_content for doc in docs])

# Define the tool
rag_tool = Tool(
    name=&quot;RetrieveCodeSnippets&quot;,
    func=retrieve_code_snippets,
    description=&quot;Retrieves relevant code snippets based on a query.&quot;
)
</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h4 id="hintwecanusetheretrievalqatoletllmcommunicatedirectlywithoutragtool">Hint: We can use the RetrievalQA to let llm communicate directly without RAG tool.</h4>
<pre><code>def setup_rag_chain(vectorstore):
    &quot;&quot;&quot;
    Sets up the Retrieval Augmented Generation (RAG) chain.
    This chain takes a query, retrieves relevant documents from the vector store,
    and then passes them to the LLM to generate an answer.
    &quot;&quot;&quot;
    print(&quot;Setting up RAG chain...&quot;)
    llm = OllamaLLM(model=&quot;deepseek-r1:7b&quot;) # local llm

    # Define a custom prompt for the LLM
    # This prompt guides the LLM on how to use the retrieved context
    custom_prompt_template = &quot;&quot;&quot;Use the following pieces of context to answer the user's question.
If you don't know the answer, just say that you don't know, don't try to make up an answer.
Keep the answer concise and relevant to the codebase.

Context:
{context}

Question:
{question}

Helpful Answer:&quot;&quot;&quot;
    CUSTOM_PROMPT = PromptTemplate(
        template=custom_prompt_template,
        input_variables=[&quot;context&quot;, &quot;question&quot;]
    )

    # Create the RetrievalQA chain
    # 'stuff' chain type puts all retrieved documents into the prompt
    qa_chain = RetrievalQA.from_chain_type(
        llm=llm,
        chain_type=&quot;stuff&quot;,
        retriever=vectorstore.as_retriever(search_kwargs={&quot;k&quot;: 5}), # Retrieve top 5 relevant documents
        return_source_documents=True, # Return the documents that were used to generate the answer
        chain_type_kwargs={&quot;prompt&quot;: CUSTOM_PROMPT}
    )
    print(&quot;RAG chain setup complete.&quot;)
    return qa_chain
    
# Usage
result = qa_chain({&quot;query&quot;: query})
</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="settinguptheagentwithmcpuse">🛠️ Setting Up the Agent with mcp-use</h3>
<p>To streamline the integration of GitLab tools into your LangChain agent, you can utilize the mcp-use library. This library simplifies the process of connecting LangChain-compatible LLMs with MCP servers, allowing for seamless tool integration without manual definitions.</p>
<h4 id="cloneandbuildgitlabmcpserver">Clone and build Gitlab MCP server:</h4>
<p>Your system needs NodeJS to build and start the server.</p>
<pre><code>git clone https://github.com/rifqi96/mcp-gitlab.git
cd mcp-gitlab
npm install
npm run build
</code></pre>
<p>After building the source code you can find the <code>index.js</code> in the build folder and use to start the local mcp server and connect with client.</p>
<h4 id="initializethemcpclient">Initialize the MCP Client:</h4>
<p>Set up the MCP client to connect to your GitLab MCP server:</p>
<pre><code>from mcp_use import MCPClient

# Init the client with local server built source configuration
config = {
  &quot;mcpServers&quot;: {
    &quot;gitlab&quot;: {
      &quot;command&quot;: &quot;node&quot;,
      &quot;args&quot;: [&quot;/path/to/mcp-gitlab/build/index.js&quot;],
      &quot;env&quot;: {
        &quot;GITLAB_API_TOKEN&quot;: &quot;YOUR_GITLAB_API_TOKEN&quot;,
        &quot;GITLAB_API_URL&quot;: &quot;https://gitlab.com/api/v4&quot;
      }
    }
  }
}

client = MCPClient.from_dict(config)
</code></pre>
<h4 id="loadtoolsfromthemcpserver">Load Tools from the MCP Server:</h4>
<p>Retrieve the available tools from the connected MCP server:</p>
<pre><code>mcp_tools = client.load_tools()
</code></pre>
<h4 id="integratetoolsintoyouragent">Integrate Tools into Your Agent:</h4>
<p>Pass the loaded tools from RAG and MCP server to your LangChain agent:</p>
<pre><code>from langchain.agents import initialize_agent
from langchain.llms import OpenAI

# Initialize your LLM
llm = OpenAI(temperature=0)

all_tools = mcp_tools + [rag_tool]

# Create the agent with the loaded tools
agent = initialize_agent(all_tools, llm, agent=&quot;cool-ai-reviewer&quot;, verbose=True)
</code></pre>
<h4 id="usetheagent">Use the Agent:</h4>
<p>Now, you can prompt your agent to perform tasks:</p>
<pre><code># Example prompt to review a merge request
prompt = &quot;Review merge request #42 and provide feedback on code quality and potential issues.&quot;

# Run the agent with the prompt
agent.run(prompt)
</code></pre>
<p>By leveraging the <code>mcp-use</code> library and RAG, you can efficiently integrate GitLab functionalities into your LangChain agent. The agent will utilize the tools to fetch merge request details and retrieve relevant code snippets, enabling it to provide comprehensive feedback. This approach enhances scalability and maintainability, especially when dealing with multiple tools or MCP servers.</p>
<!--kg-card-end: markdown--><hr><h2 id="-testing-the-agent">🧪 Testing the Agent</h2><p>To ensure the agent functions correctly:</p><p><strong>Submit a Merge Request</strong>: Create a new merge request in your GitLab repository.</p><p><strong>Run the Agent</strong>: Use the agent to review the merge request by providing the appropriate prompt.</p><p><strong>Evaluate Feedback</strong>: Assess the feedback provided by the agent for accuracy and usefulness.</p><hr><h2 id="-conclusion">🚀 Conclusion</h2><p>By integrating LangChain with GitLab via an MCP server and employing RAG for code indexing, we've built a powerful AI agent capable of automating code reviews. This setup not only saves time but also enhances code quality by providing consistent and thorough feedback.</p><p>Feel free to expand upon this foundation by adding more tools, improving the embedding model, or integrating with other services to further enhance the agent's capabilities.</p>]]></content:encoded></item><item><title><![CDATA[Enhancing Security in Mobile App Development: Best Practices and Strategies]]></title><description><![CDATA[<p>Mobile apps have become an integral part of our daily lives, from managing finances and healthcare to shopping and socializing. With this ubiquity comes a significant challenge: ensuring the security of mobile applications to protect sensitive user data and maintain trust. Cyber threats targeting mobile apps are increasingly sophisticated, making</p>]]></description><link>http://www.hapq.me:80/enhancing-security-in-mobile-app-development-best-practices-and-strategies/</link><guid isPermaLink="false">6775134bb256260463ccf3b1</guid><dc:creator><![CDATA[HàPQ]]></dc:creator><pubDate>Wed, 01 Jan 2025 10:10:55 GMT</pubDate><content:encoded><![CDATA[<p>Mobile apps have become an integral part of our daily lives, from managing finances and healthcare to shopping and socializing. With this ubiquity comes a significant challenge: ensuring the security of mobile applications to protect sensitive user data and maintain trust. Cyber threats targeting mobile apps are increasingly sophisticated, making robust security measures an essential aspect of app development.</p><figure class="kg-card kg-image-card"><img src="http://www.hapq.me/content/images/2025/01/DALL-E-2025-01-01-19.09.50---A-flat-design-illustration-of-mobile-app-security--featuring-a-smartphone-with-a-large-padlock-icon-in-the-center.-The-background-is-simple-with-abstr.jpeg" class="kg-image" alt srcset="http://www.hapq.me/content/images/size/w600/2025/01/DALL-E-2025-01-01-19.09.50---A-flat-design-illustration-of-mobile-app-security--featuring-a-smartphone-with-a-large-padlock-icon-in-the-center.-The-background-is-simple-with-abstr.jpeg 600w, http://www.hapq.me/content/images/size/w1000/2025/01/DALL-E-2025-01-01-19.09.50---A-flat-design-illustration-of-mobile-app-security--featuring-a-smartphone-with-a-large-padlock-icon-in-the-center.-The-background-is-simple-with-abstr.jpeg 1000w, http://www.hapq.me/content/images/2025/01/DALL-E-2025-01-01-19.09.50---A-flat-design-illustration-of-mobile-app-security--featuring-a-smartphone-with-a-large-padlock-icon-in-the-center.-The-background-is-simple-with-abstr.jpeg 1024w" sizes="(min-width: 720px) 720px"></figure><p>Here’s a comprehensive guide to securing mobile applications, covering key threats, best practices, and strategies for developers.</p><h3 id="understanding-mobile-app-security-threats"><strong>Understanding Mobile App Security Threats</strong></h3><p>Before implementing security measures, it’s essential to recognize the common threats mobile apps face:</p><ol><li><strong>Data Breaches</strong>: Unauthorized access to sensitive information, such as personal data, financial details, or login credentials.</li><li><strong>Malware</strong>: Malicious software designed to exploit vulnerabilities in apps, leading to unauthorized control or data theft.</li><li><strong>Man-in-the-Middle (MitM) Attacks</strong>: Interception of data transmitted between the app and server, compromising sensitive information.</li><li><strong>Reverse Engineering</strong>: Attackers decompile apps to uncover vulnerabilities or steal intellectual property.</li><li><strong>Insecure Data Storage</strong>: Storing sensitive data in plain text or improperly secured databases, making it vulnerable to attacks.</li><li><strong>Weak Authentication</strong>: Poorly implemented authentication mechanisms can allow unauthorized access.</li></ol><h3 id="best-practices-for-mobile-app-security"><strong>Best Practices for Mobile App Security</strong></h3><h4 id="1-secure-code-practices">1. <strong>Secure Code Practices</strong></h4><ul><li><strong>Minimize Vulnerabilities</strong>: Conduct regular code reviews and use static analysis tools to identify potential issues.</li><li><strong>Obfuscation and Encryption</strong>: Obfuscate your code to make reverse engineering difficult and encrypt sensitive logic.</li><li><strong>Update Regularly</strong>: Patch known vulnerabilities promptly to stay ahead of emerging threats.</li></ul><h4 id="2-implement-robust-authentication">2. <strong>Implement Robust Authentication</strong></h4><ul><li>Use multi-factor authentication (MFA) to enhance security.</li><li>Employ strong password policies and consider biometric authentication methods like fingerprint or facial recognition.</li></ul><h4 id="3-encrypt-data">3. <strong>Encrypt Data</strong></h4><ul><li>Encrypt data both at rest and in transit using protocols like HTTPS and AES.</li><li>Avoid storing sensitive data locally unless absolutely necessary, and if stored, use secure containers or encrypted databases.</li></ul><h4 id="4-secure-apis">4. <strong>Secure APIs</strong></h4><ul><li>Authenticate and authorize API requests to prevent unauthorized access.</li><li>Limit API access to trusted endpoints and employ rate limiting to prevent abuse.</li></ul><h4 id="5-protect-against-reverse-engineering">5. <strong>Protect Against Reverse Engineering</strong></h4><ul><li>Use tools like ProGuard for Android and obfuscation techniques for iOS.</li><li>Sign your app with a certificate to ensure its integrity.</li></ul><h4 id="6-conduct-regular-security-testing">6. <strong>Conduct Regular Security Testing</strong></h4><ul><li>Perform penetration testing to identify vulnerabilities from an attacker’s perspective.</li><li>Use automated tools for dynamic and static application security testing (DAST and SAST).</li></ul><h4 id="7-secure-third-party-libraries">7. <strong>Secure Third-Party Libraries</strong></h4><ul><li>Ensure third-party libraries are updated and sourced from reputable providers.</li><li>Regularly audit libraries for vulnerabilities and remove unused dependencies.</li></ul><h4 id="8-user-education">8. <strong>User Education</strong></h4><ul><li>Educate users about secure practices, such as recognizing phishing attempts and avoiding untrusted networks.</li><li>Clearly communicate the importance of app updates for maintaining security.</li></ul><h3 id="strategies-for-ongoing-security-maintenance"><strong>Strategies for Ongoing Security Maintenance</strong></h3><p><strong>Adopt a DevSecOps Approach</strong></p><ul><li>Integrate security into every stage of the development lifecycle.</li><li>Use tools and automation to enforce security standards consistently.</li></ul><p><strong>Monitor and Respond to Threats</strong></p><ul><li>Implement monitoring tools to detect unusual activity in real-time.</li><li>Have an incident response plan ready to address potential breaches swiftly.</li></ul><p><strong>Compliance and Regulations</strong></p><ul><li>Ensure compliance with data protection laws like GDPR, HIPAA, or CCPA.</li><li>Use security standards like OWASP’s Mobile Top 10 as a guideline.</li></ul><p><strong>Secure App Distribution</strong></p><ul><li>Distribute apps through official app stores to reduce the risk of tampering.</li><li>Use app store protections like Google Play Protect or Apple’s security reviews.</li></ul><h3 id="conclusion"><strong>Conclusion</strong></h3><p>Security is not a one-time effort but an ongoing process requiring vigilance and proactive measures. By following these best practices and strategies, developers can create secure mobile applications that protect user data and build trust with their audience.</p><p>As the mobile app landscape evolves, staying informed about emerging threats and security advancements is key to maintaining a robust defense. Prioritize security at every stage of development to ensure your app’s success and sustainability in an increasingly connected world.</p>]]></content:encoded></item><item><title><![CDATA[Code Review - a checklist to do it right]]></title><description><![CDATA[<p>Overall: Look for Improvement - Do Encouragement - Share best practices - Not to hold the PR - Minor things should be noted but tell developer it's skippable.</p><ol><li><strong>Design</strong>:<br>Does the code change make senses? is it a good time to add? how does it integrate with our current code</li></ol>]]></description><link>http://www.hapq.me:80/code-review-a-checklist-to-do-it-right/</link><guid isPermaLink="false">659cd723e67b057a0ba7cd70</guid><category><![CDATA[coding]]></category><category><![CDATA[swift]]></category><category><![CDATA[english]]></category><category><![CDATA[tips]]></category><dc:creator><![CDATA[HàPQ]]></dc:creator><pubDate>Tue, 09 Jan 2024 05:18:56 GMT</pubDate><content:encoded><![CDATA[<p>Overall: Look for Improvement - Do Encouragement - Share best practices - Not to hold the PR - Minor things should be noted but tell developer it's skippable.</p><ol><li><strong>Design</strong>:<br>Does the code change make senses? is it a good time to add? how does it integrate with our current code base or system?<br><br></li><li><strong>Functionality</strong><br>Determine the users of this code change: both end-users (feature impact) and developer (future use, future enhancement?)<br>Ask for a demo/UI if need to understand the reason<br>Introduce a race condition or deadlocks?<br><br></li><li><strong>Complexity</strong><br>Functions are too complex?<br>Classes are too heavy?<br>“Too complex” usually means <strong>“can’t be understood quickly by code readers.”</strong> It can also mean <strong>“developers are likely to introduce bugs when they try to call or modify this code.”</strong><br>Over-engineering: code is more generic than it needs to be, solving a future problem? future problems should be solved when it arrives not the time of PR.<br><br></li><li><strong>Test</strong><br>Ask for Unit tests, integration tests, end-to-end tests<br>Test should never test itself<br>Tests are code so that they are need to be easily maintained<br><br></li><li><strong>Naming</strong><br>Naming is hard, should pick a good name: descriptive and describe correctly the purpose<br><br></li><li><strong>Comments</strong><br>Comment WHY not WHAT to do<br>Check the necessary of the comments<br>Need to be a clear and understandable ENGLISH (don't use Japanese)<br>Comments are different from <em>documentation</em><br><br></li><li><strong>Styles</strong><br>Follow Swift Lint rules <a href="https://realm.github.io/SwiftLint/rule-directory.html" rel="nofollow">https://realm.github.io/SwiftLint/rule-directory.html</a><br>Run SonarQube before making the PR, always check SonarQube results after PR made<br><br></li><li><strong>Documentation</strong><br>Should always be updated with the code<br>Delete if code is deleted<br>Update README in case new code violates it<br><br></li><li><strong>Every line</strong><br>It's a must to go through every line of code that have been assigned to you.<br>Some codes are auto generated, but never skip human-written code.<br>For large code base, break into parts to review, note in comment for the part that reviewed or mark reviewed using Gitlab interface then can continue at another time.<br>Ask for clarification if any codes slow down your review.<br><br></li><li><strong>Context</strong><br>Sometimes need to have a broader context to tell code changes make sense: for example several lines are changes, but need to read whole class to decide correctly.<br><strong>Don’t accept PRs that degrade the code health of the system</strong><br><br></li><li><strong>Good thing</strong><br>If see something nice in the PR, tell the developer. Code reviews often emphasize on mistakes, but should offer encouragement and appreciation for good practices.</li></ol><p>Based on <a href="https://google.github.io/eng-practices/review/reviewer/looking-for.html">https://google.github.io/eng-practices/review/reviewer/looking-for.html</a><br></p>]]></content:encoded></item><item><title><![CDATA[Steps to automate deployment process for iOS applications to Test flight on any machine (p1)]]></title><description><![CDATA[<!--kg-card-begin: markdown--><h1 id="introduction">Introduction</h1>
<p>Are you tired of the manual and time-consuming process of deploying your iOS applications? Look no further. With the power of GitHub Actions, deploying iOS apps has never been easier. By automating the building, testing, and deployment tasks, you can save time and eliminate human error.</p>
<p>In this blog</p>]]></description><link>http://www.hapq.me:80/deploy-ios-application-with-github-action/</link><guid isPermaLink="false">6428c8a7e67b057a0ba7cb88</guid><category><![CDATA[english]]></category><category><![CDATA[fastlane]]></category><category><![CDATA[github-action]]></category><category><![CDATA[deployment]]></category><category><![CDATA[ci/cd]]></category><category><![CDATA[app-store]]></category><dc:creator><![CDATA[HàPQ]]></dc:creator><pubDate>Sat, 27 May 2023 12:00:44 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h1 id="introduction">Introduction</h1>
<p>Are you tired of the manual and time-consuming process of deploying your iOS applications? Look no further. With the power of GitHub Actions, deploying iOS apps has never been easier. By automating the building, testing, and deployment tasks, you can save time and eliminate human error.</p>
<p>In this blog post, we will explore how to deploy iOS applications using GitHub Actions. I'll walk through the essential elements you need to prepare, such as setting up a build machine, managing certificates and provisioning profiles. Additionally, we'll dive into the concept of environment variables and how they can simplify the configuration process.</p>
<h1 id="preparation">Preparation</h1>
<h2 id="abuildmachinewithinstalledxcode">A build machine with Installed Xcode</h2>
<p>To build and package an iOS application, you need a dedicated build machine. Set up your correct version of Xcode. I would recommend to use Xcodes tool to manage multiples XCode versions on your build machine.<br>
<a href="https://github.com/XcodesOrg/xcodes">https://github.com/XcodesOrg/xcodes</a></p>
<h2 id="codesigningcertificate">Code signing certificate</h2>
<p>I assume you know how to create/store/export a deployment certificate. Our certificate <code>.p12</code> will be encoded to base64 string and stored as environment variables (please find detail in next segment). Then we create a bash script <code>install_certificate.sh</code> to</p>
<ul>
<li>Create new keychain.</li>
<li>Install the certificate to the keychain.</li>
<li>Unlock the keychain to allow xcode access it to use the cert when exporting ipa.</li>
</ul>
<p>Here is the content of the sript</p>
<pre><code>#!/usr/bin/env sh

CERTIFICATE_P12=certificate.p12

# Decoding the base 64 cert and write it to a file
echo $CERT_BASE64 | base64 --decode &gt; $CERTIFICATE_P12

# Creating new keychain with a password
security create-keychain -p $BUILD_KEY_CHAIN_PASSWORD $BUILD_KEY_CHAIN_NAME
# Setting the keychain as default
security list-keychain -s $BUILD_KEY_CHAIN_NAME
security default-keychain -s $BUILD_KEY_CHAIN_NAME
# Unlock the keychain
security unlock-keychain -p $BUILD_KEY_CHAIN_PASSWORD $BUILD_KEY_CHAIN_NAME
# start manipulate it
security set-keychain-settings $BUILD_KEY_CHAIN_NAME
# import the cert content, $2 is the certificate password
security import $CERTIFICATE_P12 -k $BUILD_KEY_CHAIN_NAME -P $CERT_PASSWORD -T /usr/bin/codesign;

# we allow codesign to use the keychain above
# codesign is the tool that xcode uses to sign our app
# https://stackoverflow.com/questions/39868578/security-codesign-in-sierra-keychain-ignores-access-control-settings-and-ui-p
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $BUILD_KEY_CHAIN_PASSWORD $BUILD_KEY_CHAIN_NAME

# Then just remove the file
rm -fr *.p12
</code></pre>
<p>This bash script will be called like</p>
<pre><code>chmod +x install_profile.sh &amp;&amp; ./install_certificate.sh
</code></pre>
<h2 id="provisioningprofile">Provisioning profile</h2>
<p>It's simpler with provisioning profile. We just need to write it to the file with correct name in a correct folder. Let make another bash script <code>install_profile.sh</code></p>
<pre><code>PROFILE_FILE=$1.mobileprovision

echo $PROVISIONING_BASE64 | base64 --decode &gt; $PROFILE_FILE

cp ${PROFILE_FILE} &quot;$HOME/Library/MobileDevice/Provisioning Profiles/$1.mobileprovision&quot;

rm -fr *.mobileprovision
</code></pre>
<p>Nice, really easy to understand.</p>
<p>So your machine is ready to build because it has the cert &amp; provisioning. From next steps, I will share what i achieve to automate the whole process.</p>
<h2 id="fastlanescript">Fastlane script</h2>
<p>I will give you an example of the lane I made, please adjust depends on your team need.</p>
<pre><code>desc &quot;PROD Build and upload to AppStore if need&quot;
lane :prod_appstore do |options|
  $version = options[:version] || get_version_number(xcodeproj: ENV[&quot;PROJECT_IDENTIFIER&quot;], target: ENV[&quot;BUILD_TARGET&quot;])
  $build = options[:build] || get_build_number(xcodeproj: ENV[&quot;PROJECT_IDENTIFIER&quot;])

  prepare(
    plist: ENV[&quot;PLIST_FILE_NAME&quot;], 
    version: $version, 
    build: $build, 
    team_id: ENV[&quot;APP_STORE_TEAM_ID&quot;], 
    code_sign: ENV[&quot;CODE_SIGN_IDENTITY_NAME&quot;], 
    provisioning: ENV[&quot;PROVISIONING_NAME&quot;], 
    bundle_id: ENV[&quot;APP_BUNDLE_ID&quot;],
    xcode_version: options[:xcode_version]
  )
  gym(
    scheme: scheme,
    export_method: &quot;app-store&quot;,
    export_team_id: ENV[&quot;APP_STORE_TEAM_ID&quot;],
    export_options: {
      provisioningProfiles: {
        ENV[&quot;APP_BUNDLE_ID&quot;] =&gt; ENV[&quot;PROVISIONING_NAME&quot;]
      },
      manageAppVersionAndBuildNumber: false
    },
    codesigning_identity: ENV[&quot;CODE_SIGN_IDENTITY_NAME&quot;],
    clean: true,
    output_directory: &quot;./builds&quot;,
    skip_build_archive: false,
    output_name: &quot;just-a-name.ipa&quot;
  )
  verify_build(
    provisioning_type: &quot;distribution&quot;,
    bundle_identifier: ENV[&quot;APP_BUNDLE_ID&quot;],
    team_identifier: ENV[&quot;APP_STORE_TEAM_ID&quot;]
  )
end

def prepare(plist:, version:, build:, team_id:, code_sign:, provisioning:, bundle_id:, xcode_version:)
  puts &quot;Configure Xcode and Project&quot;
  if xcode_version
    xcversion(version: xcode_version)
  end

  ensure_git_status_clean
  increment_version_number(version_number: version)
  increment_build_number(build_number: build)

  update_code_signing_settings(
    use_automatic_signing: false,
    targets: [ENV[&quot;BUILD_TARGET&quot;]],
    path: ENV[&quot;PROJECT_IDENTIFIER&quot;],
    team_id: team_id,
    code_sign_identity: code_sign, 
    profile_name: provisioning,
  )

  update_app_identifier(
    plist_path: plist,
    app_identifier: bundle_id
  )

  update_project_team(
    path: ENV[&quot;PROJECT_IDENTIFIER&quot;],
    targets: [ENV[&quot;BUILD_TARGET&quot;]],
    teamid: team_id
  )

  cocoapods(clean_install: true)
end

</code></pre>
<h2 id="fastlaneuploadtoappstore">Fastlane upload to App Store</h2>
<p>This is sample steps in fastlane.</p>
<pre><code>api_key = app_store_connect_api_key(
  key_id: ENV[&quot;APP_STORE_KEY_ID&quot;],
  issuer_id: ENV[&quot;APP_STORE_ISSUER_ID&quot;],
  key_content: ENV[&quot;APP_STORE_KEY&quot;],
  is_key_content_base64: true,
  duration: 1200, # optional (maximum 1200)
  in_house: false
)

upload_to_testflight(
  ipa: &quot;./builds/just-a-name.ipa&quot;, 
  skip_submission: true,
  skip_waiting_for_build_processing: true,
  api_key: api_key
)
end
</code></pre>
<p>I wrote details about some ways to upload in this blog<br>
<a href="https://www.hapq.me/two-ways-to-upload-ipa-to-testflight-with-fastlane/">https://www.hapq.me/two-ways-to-upload-ipa-to-testflight-with-fastlane/</a></p>
<h1 id="environmentvariables">Environment Variables</h1>
<p>This is the thing I missed for long time in my whole career. I found out these variables are live inside a terminal session. We can store it by running this command</p>
<pre><code>export MY_FUTURE_USE_VARIABLE=Hello
</code></pre>
<p>then when need to use, add a dollar sign before the name</p>
<pre><code>echo $MY_FUTURE_USE_VARIABLE
</code></pre>
<p>Really simple but i don't know why I missed this stuff.</p>
<p>In any CI/CD tools such as Gitlab, Jenkins, Github action... there're always predefined environment variables that tell you about the job, the pull request, the machine etc.... You can access it anywhere anytime you want.<br>
Gitlab CI shared details of them: <a href="https://docs.gitlab.com/ee/ci/variables/predefined_variables.html">https://docs.gitlab.com/ee/ci/variables/predefined_variables.html</a></p>
<h1 id="githubactiongitlabci">Github action &amp; Gitlab CI</h1>
<p>please wait for part 2 of this serie.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[One of  my "on paper" business plan in 2020]]></title><description><![CDATA[<p><strong><strong>Tokyo Bicycle Rent Application (TBRA) for Olympic 202</strong>1</strong><br><strong><strong>2020</strong></strong></p><p><strong>TBRA</strong> presents a superb opportunity in my local market for local bicycle providers for the 2020 Olympics. There are no direct competitors for now. Revenues are expected as <strong>330.000$</strong> for first year and will keep stable as <strong>500.000$</strong></p>]]></description><link>http://www.hapq.me:80/one-of-my-business-idea-but-never-come-true/</link><guid isPermaLink="false">6432a510e67b057a0ba7cb96</guid><category><![CDATA[english]]></category><category><![CDATA[business idea]]></category><category><![CDATA[lifestyle]]></category><category><![CDATA[planning]]></category><dc:creator><![CDATA[HàPQ]]></dc:creator><pubDate>Sun, 09 Apr 2023 12:13:55 GMT</pubDate><content:encoded><![CDATA[<p><strong><strong>Tokyo Bicycle Rent Application (TBRA) for Olympic 202</strong>1</strong><br><strong><strong>2020</strong></strong></p><p><strong>TBRA</strong> presents a superb opportunity in my local market for local bicycle providers for the 2020 Olympics. There are no direct competitors for now. Revenues are expected as <strong>330.000$</strong> for first year and will keep stable as <strong>500.000$</strong> for each year later.</p><figure class="kg-card kg-image-card"><img src="http://www.hapq.me/content/images/2023/04/himechari-share-cycle-himeji_gdl-770x578-1.jpg" class="kg-image" alt srcset="http://www.hapq.me/content/images/size/w600/2023/04/himechari-share-cycle-himeji_gdl-770x578-1.jpg 600w, http://www.hapq.me/content/images/2023/04/himechari-share-cycle-himeji_gdl-770x578-1.jpg 770w" sizes="(min-width: 720px) 720px"></figure><p><strong>BUSINESS DESCRIPTION</strong><br><strong>Product:</strong><br><strong>TBRA </strong>is a mobile application for travelers to find nearby bicycle renting places. They can go directly by foot and rent the bike they like.<br></p><p><strong>Manage and operation:</strong><br><strong>TBRA </strong>will have zero managers and 5 employees. <strong>TBRA</strong> team will maintain systems, connect bicycle renting vendors, and do SEO for the application.<br></p><p><strong>MARKETING</strong><br><strong>Market research</strong><br>Opportunity: <strong>TBRA</strong> presents an exciting opportunity in my local market with travelers who come to Tokyo in 2021 to support their favorite Olympic sport teams. For the first year, we will spend money to claim users and get connections with local bike vendors.</p><p>On <strong>Friday, July 23, 2021, </strong>the 2020 Olympics will be held in Tokyo. All people around the world will go to Japan and the need for bike services will increase. According to simple statistics of the 2016 Olympics in Rio De Janeiro, around 6.6 million travelers arrived in Brazil.<br></p><p>“<em>A total 6.6 million international travelers visited Brazil last year, the tourism ministry said, 4.8 percent higher than the year before.</em>” - <a href="https://www.voanews.com/americas/brazil-sees-record-number-tourists-2016-boosted-olympics#:~:text=SAO%20PAULO%20%2D%20Brazil%20saw%20a,higher%20than%20the%20year%20before.">VOA news</a><br></p><p>If we can get <strong>5%</strong> of these tourists to use our applications, taking 1$ per each trip, our revenue totally will be <strong>330.000$.</strong><br></p><p><strong>COMPETITIVE ANALYSIS:</strong><br>These are some good applications I found: <strong>NextBike, oBike-Stationless. </strong>One of them has around 1M downloads but they don’t have connections/vendors in Tokyo for now. We can research their business flow/application flow to contribute to our business model.<br></p><p><strong>FINANCIALS:</strong><br><strong>StartUp Costs:</strong></p><ul><li>Application development : backend, map service, mobile app. Salary for developers: <strong>3 developers * 50k$ = 150k$</strong></li><li>Station: <strong>Zero</strong> cost, for the first year, we can connect with existing local vendors.</li><li>Bike: <strong>Zero</strong> costs, from vendors, we take a small fee for each user’s ride.</li><li>Application Infrastructure: <strong>10k$</strong></li><li>Vendors connection: <strong>40k$</strong></li><li>Risks: problem handle <strong>30k$</strong></li></ul><p><em>Total cost: <strong>230.000$</strong></em><br><br><strong>Second years and laters:</strong><br><strong>Sales &amp; Revenue Projections:</strong><br><em>1st year: 330.000$</em><br><em>2nd year: 500.000$</em><br><em>3rd year: 500.000$</em><br>Revenues are expected to increase from 330k to 500k in three years.<br><br><strong>Costs Projections:</strong><br><em>Startup Costs: 230.000$</em><br><em>2nd year: 300.000$</em><br><em>3rd year: 200.000$</em><br>Costs are expected to increase from 230k to 300k and decrease to 200k in the first 3 years.<br><br><strong>Profit Statement Expected:</strong><br><em>1st year: 100.000$</em><br><em>2nd year: 200.000$</em><br><em>3rd year: 300.000$</em><br>Income is expected to go up from 100k to 300k in three years.</p><figure class="kg-card kg-image-card"><img src="http://www.hapq.me/content/images/2023/04/meta-chart.png" class="kg-image" alt srcset="http://www.hapq.me/content/images/size/w600/2023/04/meta-chart.png 600w, http://www.hapq.me/content/images/size/w1000/2023/04/meta-chart.png 1000w, http://www.hapq.me/content/images/2023/04/meta-chart.png 1200w" sizes="(min-width: 720px) 720px"></figure><p>Well it's the past, but when looking back I feel it's really satisfying. At least i put something on paper. Today I've just asked ChatGPT for improvements, it gave mesome ideas?</p><!--kg-card-begin: markdown--><blockquote>
<p><strong>Clarify the value proposition</strong>: While it's clear that TBRA will allow travelers to easily find and rent bicycles, it's not clear what the benefits are for the local bike vendors. What incentive do they have to partner with TBRA? You may want to consider offering a revenue-sharing model or other benefits to encourage vendor participation.</p>
<p><strong>Conduct more market research</strong>: While it's helpful to have statistics on the number of travelers who visited Brazil during the 2016 Olympics, it's important to gather more detailed information about the target market in Tokyo. For example, what are the demographics of travelers who are likely to use TBRA? What are their preferences and pain points? This information will help you refine your marketing and product strategies.</p>
<p><strong>Develop a more detailed marketing plan</strong>: It's great to have a goal of getting 5% of Olympic tourists to use TBRA, but how exactly do you plan to achieve this? Consider using a mix of online advertising, social media, influencer marketing, and partnerships with travel agencies or hotels to increase awareness and drive downloads.</p>
<p><strong>Consider the costs of ongoing maintenance and support</strong>: While your startup costs seem reasonable, it's important to factor in the ongoing costs of maintaining and updating the app, providing customer support, and managing relationships with vendors. These costs may be higher than you anticipate, so it's important to plan accordingly.</p>
<p><strong>Explore potential partnerships</strong>: In addition to partnering with local bike vendors, consider other potential partners who could help you reach your target market. For example, you might partner with a tourism board or travel app to promote TBRA to travelers.</p>
<p>Overall, your business idea has potential, but it's important to do more research and planning to ensure that it is successful. Good luck!</p>
</blockquote>
<!--kg-card-end: markdown--><p>Potential idea? I'm too bad, has no courage, no brave enough to challenge myself?</p>]]></content:encoded></item><item><title><![CDATA[The Golden Journey: a quest to reclaim heritage and return to Mars]]></title><description><![CDATA[<p>"Once upon a time, in a distant galaxy, there was a planet called Mars that was home to an advanced and curious race of beings. They were always exploring and discovering new things, and one day they stumbled upon a strange metal that they had never seen before. The metal</p>]]></description><link>http://www.hapq.me:80/the-golden-journey-a-quest-to-reclaim-heritage-and-return-to-mars/</link><guid isPermaLink="false">63e3912fe67b057a0ba7cb60</guid><category><![CDATA[chatgpt]]></category><category><![CDATA[story]]></category><category><![CDATA[mars]]></category><category><![CDATA[midjourney]]></category><dc:creator><![CDATA[HàPQ]]></dc:creator><pubDate>Wed, 08 Feb 2023 12:47:02 GMT</pubDate><content:encoded><![CDATA[<p>"Once upon a time, in a distant galaxy, there was a planet called Mars that was home to an advanced and curious race of beings. They were always exploring and discovering new things, and one day they stumbled upon a strange metal that they had never seen before. The metal was golden in color and seemed to have properties unlike anything they had ever encountered. They named it the "Golden Element" and started to study its properties.</p><figure class="kg-card kg-image-card"><img src="http://www.hapq.me/content/images/2023/02/Spock_two_scientists_are_living_on_Mars_and_studying_about_a_go_1d2ef2c4-5f93-4ee4-90d6-2099c26eb1d9.png" class="kg-image" alt srcset="http://www.hapq.me/content/images/size/w600/2023/02/Spock_two_scientists_are_living_on_Mars_and_studying_about_a_go_1d2ef2c4-5f93-4ee4-90d6-2099c26eb1d9.png 600w, http://www.hapq.me/content/images/2023/02/Spock_two_scientists_are_living_on_Mars_and_studying_about_a_go_1d2ef2c4-5f93-4ee4-90d6-2099c26eb1d9.png 994w" sizes="(min-width: 720px) 720px"></figure><p>Years of research later, the Martians discovered that the Golden Element had the ability to fuel their spaceships, making their interplanetary travels faster and easier. Excited by this new discovery, they built a magnificent spaceship entirely out of the Golden Element, and set out to explore the universe.</p><p>As the Martians traveled from planet to planet, they eventually came across a small, blue planet with abundant resources. They decided to make it their new home, and they established a colony on the planet, which they named Earth. Over time, the Martians thrived on Earth and evolved into the human race.</p><figure class="kg-card kg-image-card"><img src="http://www.hapq.me/content/images/2023/02/Spock_a_golden_spaceship_falling_down_to_earth_in_acient_time_8db4771e-4988-474a-82a6-5d2cf410a287.png" class="kg-image" alt srcset="http://www.hapq.me/content/images/size/w600/2023/02/Spock_a_golden_spaceship_falling_down_to_earth_in_acient_time_8db4771e-4988-474a-82a6-5d2cf410a287.png 600w, http://www.hapq.me/content/images/2023/02/Spock_a_golden_spaceship_falling_down_to_earth_in_acient_time_8db4771e-4988-474a-82a6-5d2cf410a287.png 920w" sizes="(min-width: 720px) 720px"></figure><p>However, as the human race progressed, they began to forget their origins and the significance of the Golden Element. But a small group of humans, who had kept the knowledge passed down from their Martian ancestors, formed a secret society to collect the Golden Element and rebuild the spaceship to return to their ancestral home on Mars.</p><p>Despite facing challenges and opposition, the society persevered in their mission. They traveled far and wide, collecting every scrap of the Golden Element they could find, refining it and using it to rebuild the magnificent spaceship. And after many long years of hard work, the spaceship was finally complete.</p><p>The day finally arrived for the humans to embark on their journey back to Mars. As they lifted off from the Earth and set course for their ancestral home, they felt a sense of pride and belonging. They were finally going back to where they came from, to reclaim their heritage and build a new civilization on Mars.</p><figure class="kg-card kg-image-card"><img src="http://www.hapq.me/content/images/2023/02/Spock_The_day_finally_arrived_for_the_humans_to_embark_on_their_ba3b131b-114d-4516-9531-28a6eac826e4.png" class="kg-image" alt srcset="http://www.hapq.me/content/images/size/w600/2023/02/Spock_The_day_finally_arrived_for_the_humans_to_embark_on_their_ba3b131b-114d-4516-9531-28a6eac826e4.png 600w, http://www.hapq.me/content/images/2023/02/Spock_The_day_finally_arrived_for_the_humans_to_embark_on_their_ba3b131b-114d-4516-9531-28a6eac826e4.png 920w" sizes="(min-width: 720px) 720px"></figure><p>And so, the story of the human race and their journey back to their ancestral home on Mars comes full circle. A tale of determination, perseverance, and the power of knowledge passed down from generation to generation."</p><p>This story is written by chatGPT based on my idea. Images are generated by MidJourney.</p>]]></content:encoded></item><item><title><![CDATA[The drawbacks of Client-side load balancing: complexity, security risks and more]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p><strong>Client-side load balancing</strong> is a method of distributing network traffic across multiple servers by using a client device <em>to determine the most efficient server to handle a request</em>. While this method can provide some benefits, such as reducing the load on a single server and improving overall performance, it also</p>]]></description><link>http://www.hapq.me:80/the-drawbacks-of-client-side-load-balancing-complexity-security-risks-and-more/</link><guid isPermaLink="false">63c699e8e67b057a0ba7cb13</guid><category><![CDATA[client-side load balancing]]></category><category><![CDATA[english]]></category><category><![CDATA[system-design]]></category><dc:creator><![CDATA[HàPQ]]></dc:creator><pubDate>Tue, 17 Jan 2023 13:06:23 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p><strong>Client-side load balancing</strong> is a method of distributing network traffic across multiple servers by using a client device <em>to determine the most efficient server to handle a request</em>. While this method can provide some benefits, such as reducing the load on a single server and improving overall performance, it also has several disadvantages that should be considered before implementing it in a network.</p>
<p>My team is implementing it and I don't like beacause of some drawbacks.</p>
<h2 id="networkingcomplexityincreasing">Networking Complexity increasing</h2>
<p>One major disadvantage of client load balancing is that it can increase the complexity of a network.</p>
<p>In client-side load balancing, the client device is responsible for <strong>determining the most efficient server to handle a request</strong>, it must have knowledge of all available servers and their current load. This requires <strong>significant configuration and maintenance</strong>, which can be time-consuming and prone to errors. For example, the client device needs to be configured with the IP addresses and ports of all available servers, and it must also be updated with new server information as servers are added or removed from the network.</p>
<p>Additionally, client-side load balancing can also <strong>increase the complexity of network troubleshooting and monitoring</strong>. As the client device is responsible for determining the server to handle a request, it can be <strong>difficult to determine the root cause of issues</strong> that arise. It can be <strong>challenging to identify the specific server that is causing problems</strong>, and it can also be difficult to determine if the issue is with the client device or with one of the servers.</p>
<p>Furthermore, client-side load balancing can also <strong>create additional complexity when it comes to deploying updates and new features</strong>. Since the client device is responsible for determining the server to handle a request, it must be updated and configured with new features and updates at the same time as the servers. This can be difficult to coordinate and can create additional downtime for users.</p>
<h2 id="securityrisks">Security Risks</h2>
<p>Another disadvantage of client load balancing is that it can increase the risk of security breaches. Since the client device is responsible for determining the server to handle a request, <strong>it must have access to sensitive information such as server IP addresses and load data</strong>. This can make <strong>the client device a prime target for hackers</strong>, who can use this information to launch attacks on the network.</p>
<h2 id="dataloss">Dataloss</h2>
<p>Another problem is that client load balancing can increase the risk of data loss. <em>Since client devices are not aware of the state of the servers, they may send requests to servers that are down or undergoing maintenance</em>. This can result in <strong>data loss</strong> or <strong>corruption</strong>, which can be costly and time-consuming to recover from.</p>
<p>Additionally, client-side load balancing can also make it more difficult to implement security measures such as firewalls and SSL offloading. Since the client device is responsible for determining the server to handle a request, it must have access to all servers on the network. This can make it <strong>difficult to implement security measures</strong> that are specific to a single server, and can also make it more difficult to monitor and troubleshoot security issues.</p>
<h2 id="bottleneck">Bottleneck</h2>
<p>Additionally, client load balancing can create bottlenecks. If the client is unable to handle the load of the balancing process, it can become overwhelmed and slow down, creating a bottleneck in the network. This can <strong>reduce the overall performance</strong> of the network and create a <strong>poor user experience</strong>.</p>
<p>In conclusion, while client load balancing can provide some benefits, it also has several disadvantages that should be considered before implementing it in a network. These include increased complexity, security risks, data loss, and bottleneck problems. It's important to weigh the pros and cons of this method before implementing it, and to consider alternative solutions, such as server-side load balancing, which may be more appropriate for certain environments.</p>
<p>This blog powered by <a href="https://chat.openai.com/chat">ChatGPT</a></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Mastering view modifiers in SwiftUI: tips and tricks]]></title><description><![CDATA[<!--kg-card-begin: markdown--><h2 id="introductiontoviewmodifiersinswiftui">Introduction to view modifiers in SwiftUI</h2>
<p>SwiftUI is a powerful framework for building user interfaces on Apple platforms. One of the key features of SwiftUI is the ability to use view modifiers to modify the appearance and behavior of views.</p>
<p>View modifiers are methods that are applied to a view</p>]]></description><link>http://www.hapq.me:80/mastering-view-modifiers-in-swiftui-tips-and-tricks/</link><guid isPermaLink="false">63c55007e67b057a0ba7cabf</guid><category><![CDATA[swift]]></category><category><![CDATA[swiftUI]]></category><category><![CDATA[chatgpt]]></category><category><![CDATA[tips]]></category><category><![CDATA[english]]></category><dc:creator><![CDATA[HàPQ]]></dc:creator><pubDate>Mon, 16 Jan 2023 13:35:37 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="introductiontoviewmodifiersinswiftui">Introduction to view modifiers in SwiftUI</h2>
<p>SwiftUI is a powerful framework for building user interfaces on Apple platforms. One of the key features of SwiftUI is the ability to use view modifiers to modify the appearance and behavior of views.</p>
<p>View modifiers are methods that are applied to a view to change its properties. They are very similar to the way CSS styles are applied to HTML elements. The main difference is that view modifiers in SwiftUI are applied to a view's properties directly, rather than through a separate stylesheet.</p>
<h2 id="builtinviewmodifiers">Built-in View Modifiers</h2>
<p>One of the most commonly used built-in view modifiers is the <code>.frame()</code> modifier, which is used to change the size and position of a view. For example, if you have a button that you want to make larger, you can use the <code>.frame() </code>modifier to change its width and height.</p>
<p>Another commonly used view modifier is the <code>.background()</code> modifier, which is used to change the background color of a view. For example, if you have a text field that you want to make red, you can use the <code>.background()</code> modifier to change its background color to red.</p>
<pre><code>TextField(&quot;Enter your name&quot;, text: $name)
    .background(Color.red)
</code></pre>
<p>You can also use a .background() modifier to apply gradient background.</p>
<pre><code>Text(&quot;Hello World&quot;)
    .background(LinearGradient(gradient: Gradient(colors: [.white, .black]), startPoint: .top, endPoint: .bottom))
</code></pre>
<p>You can also use this modifier multiple times to apply multiple background colors to a single view. The last applied modifier will be visible.</p>
<pre><code>Text(&quot;Hello World&quot;)
    .background(Color.red)
    .background(Color.yellow)
</code></pre>
<p>In this example, the text will have a yellow background color, because it was the last one applied.</p>
<h2 id="customviewmodifiers">Custom View Modifiers</h2>
<p>In addition to these built-in view modifiers, you can also create your own custom view modifiers. These can be used to encapsulate a set of properties and apply them to multiple views. For example, you can create a view modifier that applies a rounded corner and a shadow to a view.</p>
<p>To create a custom view modifier, you can define a struct that conforms to the ViewModifier protocol and implement the body property.</p>
<pre><code>struct RoundedCorners: ViewModifier {
    func body(content: Content) -&gt; some View {
        content
            .clipShape(RoundedRectangle(cornerRadius: 20))
            .shadow(radius: 5)
    }
}
</code></pre>
<p>You can then use this custom view modifier by calling the <code>.modifier()</code> method on a view.</p>
<pre><code>Text(&quot;Hello World&quot;)
    .modifier(RoundedCorners())
</code></pre>
<p>Here is another example to utilise custom view modifier.</p>
<pre><code>struct Padding: ViewModifier {
    let padding: CGFloat

    func body(content: Content) -&gt; some View {
        content
            .padding(padding)
    }
}
</code></pre>
<p>You can also chain multiple custom view modifiers together. For example, you can add a rounded corner and padding to a view like this:</p>
<pre><code>Text(&quot;Hello World&quot;)
    .modifier(RoundedCorners())
    .modifier(Padding(padding: 20))
</code></pre>
<p>Custom view modifiers can also take parameters like the above example. You can also pass a binding variable as a parameter to a custom view modifier, this way you can change the value of the parameter in real time</p>
<pre><code>struct Padding: ViewModifier {
    @Binding var padding: CGFloat

    func body(content: Content) -&gt; some View {
        content
            .padding(padding)
    }
}

struct ContentView: View {
    @State var padding: CGFloat = 20
    var body: some View {
        VStack {
            Text(&quot;Hello World&quot;)
                .modifier(Padding(padding: $padding))
            Slider(value: $padding, in: 0...50)
        }
    }
}

</code></pre>
<p>In this example, the Text will have 20 points of padding, but you can change it in real time by moving the slider.</p>
<p>As you can see, custom view modifiers are a great way to encapsulate common functionality and apply it to multiple views in a consistent and reusable way. They are also powerful tools for creating dynamic and responsive user interfaces.</p>
<h1 id="conclusion">Conclusion</h1>
<p>In conclusion, view modifiers are a powerful feature of SwiftUI that allow you to easily change the appearance and behavior of views. They are similar to CSS styles in HTML, but are applied directly to a view's properties. You can also create your own custom view modifiers to encapsulate a set of properties and apply them to multiple views.</p>
<p>This blog powered by ChatGPT <a href="https://chat.openai.com/chat">https://chat.openai.com/chat</a></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Some useful Newrelic queries for mobile app (NRQL)]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>If you read this article you may know about <a href="https://newrelic.com">https://newrelic.com</a>.<br>
It's powerful tool to monitor your system performance, from Frontend to Backend or database.</p>
<p>To prepare for new version release, we need to monitor status of our latest app to find issues that can not detected in testing</p>]]></description><link>http://www.hapq.me:80/some-useful-newrelics-query-nrql/</link><guid isPermaLink="false">631016f6e67b057a0ba7c8ed</guid><category><![CDATA[newrelics]]></category><category><![CDATA[english]]></category><category><![CDATA[coding]]></category><category><![CDATA[analytics]]></category><dc:creator><![CDATA[HàPQ]]></dc:creator><pubDate>Thu, 01 Sep 2022 02:53:04 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>If you read this article you may know about <a href="https://newrelic.com">https://newrelic.com</a>.<br>
It's powerful tool to monitor your system performance, from Frontend to Backend or database.</p>
<p>To prepare for new version release, we need to monitor status of our latest app to find issues that can not detected in testing phase, and can react to incident faster.</p>
<p>Below are some queries that i'm using...</p>
<ol>
<li>App Launches - how many device launched your current version</li>
</ol>
<pre><code>FROM MobileSession SELECT uniqueCount(deviceUuid) FACET appVersion WHERE appVersion = '9.9.9' AND appName = 'sample-ios-app' TIMESERIES AUTO SINCE '2022-08-31 00:00:00'
</code></pre>
<ol start="2">
<li>Upgraded From - which app version is upgraded to your newest version</li>
</ol>
<pre><code>FROM MobileSession SELECT count(upgradeFrom) WHERE appVersion = '9.9.9' AND appName = 'sample-ios-app' FACET upgradeFrom TIMESERIES AUTO SINCE last week
</code></pre>
<ol start="3">
<li>Crash Count and crash ratio</li>
</ol>
<pre><code>FROM MobileCrash SELECT count(*)  AS 'Crash Count' WHERE appVersion = '9.9.9' AND appName = 'sample-ios-app' TIMESERIES AUTO SINCE 1 day ago
</code></pre>
<pre><code>FROM MobileSession, MobileCrash SELECT percentage(uniqueCount(sessionId), WHERE category = 'Crash') AS `Crash Rate` WHERE appVersion = '9.9.9' AND appName = 'sample-ios-app' TIMESERIES AUTO SINCE 1 day ago
</code></pre>
<ol start="4">
<li>Crashes by all Versions</li>
</ol>
<pre><code>FROM MobileCrash SELECT count(*) WHERE appName = 'sample-ios-app' WHERE crashFingerprint NOT IN () FACET `appVersion` TIMESERIES SINCE '2022-08-31 00:00:00'
</code></pre>
<pre><code>FROM MobileSession, MobileCrash SELECT percentage(uniqueCount(sessionId), WHERE category = 'Crash')  WHERE appName = 'sample-ios-app' FACET `appVersion` TIMESERIES SINCE '2022-08-31 00:00:00'
</code></pre>
<ol start="5">
<li>App Launches - all versions</li>
</ol>
<pre><code>FROM MobileSession SELECT uniqueCount(deviceUuid) FACET appVersion WHERE appVersion not like '%dirty' AND appName = 'sample-ios-app' TIMESERIES AUTO SINCE this week
</code></pre>
<ol start="6">
<li>UUID by App Versions</li>
</ol>
<pre><code>FROM Mobile SELECT abs(uniqueCount(deviceUuid)) WHERE appName like 'sample-ios-app' FACET appVersion SINCE this month
</code></pre>
<p>Hope those queries can help.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Two ways to Upload an ipa to Testflight with Fastlane]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>After making builds with Fastlane and exported a valid <code>.ipa</code>, you need to automate the process of uploading the file to Testflight. You can do it in two ways with <code>upload_to_testflight</code> (or <code>pilot</code>) action.</p>
<h2 id="withappstoreconnectapikey">With AppStore Connect API key</h2>
<blockquote>
<p><a href="https://docs.fastlane.tools/app-store-connect-api/#using-fastlane-api-key-json-file">https://docs.fastlane.tools/app-store-connect-api/#using-fastlane-api-key-json-file</a></p>
</blockquote>
<p>The <code>upload_to_</code></p>]]></description><link>http://www.hapq.me:80/two-ways-to-upload-ipa-to-testflight-with-fastlane/</link><guid isPermaLink="false">62dd530ce67b057a0ba7c7f3</guid><category><![CDATA[fastlane]]></category><category><![CDATA[jenkins]]></category><category><![CDATA[english]]></category><category><![CDATA[ci/cd]]></category><category><![CDATA[app store connect api]]></category><category><![CDATA[tool]]></category><dc:creator><![CDATA[HàPQ]]></dc:creator><pubDate>Sun, 24 Jul 2022 15:08:16 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>After making builds with Fastlane and exported a valid <code>.ipa</code>, you need to automate the process of uploading the file to Testflight. You can do it in two ways with <code>upload_to_testflight</code> (or <code>pilot</code>) action.</p>
<h2 id="withappstoreconnectapikey">With AppStore Connect API key</h2>
<blockquote>
<p><a href="https://docs.fastlane.tools/app-store-connect-api/#using-fastlane-api-key-json-file">https://docs.fastlane.tools/app-store-connect-api/#using-fastlane-api-key-json-file</a></p>
</blockquote>
<p>The <code>upload_to_testflight</code> action need an App store connect api key to work. It's result of this action <code>app_store_connect_api_key</code>, the action for creating a JWT from a <code>.p8</code> file.</p>
<pre><code>lane :release do
  api_key = app_store_connect_api_key(
    key_id: &quot;Q58Q4Y9A5W&quot;,
    issuer_id: &quot;69a6de95-7865-47e3-e053-5b8c7c11a4d1&quot;,
    key_filepath: &quot;./AuthKey_Q58Q4Y9A5W.p8&quot;,
    duration: 1200, # optional (maximum 1200)
    in_house: false # optional but may be required if using match/sigh
  )

  upload_to_testflight(
    api_key: api_key,
    skip_submission: true,
    ipa: &quot;#{ipa_path}&quot;,
    skip_waiting_for_build_processing: true,
  )
end
</code></pre>
<p><code>.p8</code> file, <code>key_id</code>, <code>issuer_id</code> are all easily get from App Store Connect webpage.</p>
<p>Login to your developer account, go to Users and Access, then find Request Access button in Keys tab.</p>
<p><img src="http://www.hapq.me/content/images/2022/07/Screen-Shot-2022-07-24-at-23.18.53.png" alt="Screen-Shot-2022-07-24-at-23.18.53"></p>
<p>After requesting access, you can Generate any Key for some roles, use Issue ID, KeyID and Download API key (as an <code>.p8</code> file) for <code>app_store_connect_api_key</code> lane above.</p>
<p><img src="http://www.hapq.me/content/images/2022/07/Screen-Shot-2022-07-24-at-23.26.05.png" alt="Screen-Shot-2022-07-24-at-23.26.05"></p>
<p><img src="http://www.hapq.me/content/images/2022/11/Screen-Shot-2022-07-24-at-23.31.34.png" alt="Screen-Shot-2022-07-24-at-23.31.34"></p>
<p>There's another way to specific <code>.p8</code> key for <code>app_store_connect_api_key</code> lane. It's using <code>key_content</code> instead of <code>key_filepath</code>. In that case, you need to modify the <code>.p8</code>'s content by replacing all new lines with <code>\n</code>, to make it in one line.</p>
<p>For example:<br>
Default content from App Store Connect:</p>
<pre><code>-----BEGIN PRIVATE KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
EEEEEEEEEEEEE
-----END PRIVATE KEY-----
</code></pre>
<p>change to:</p>
<pre><code>-----BEGIN PRIVATE KEY-----\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD\nEEEEEEEEEEEEE\n-----END PRIVATE KEY-----
</code></pre>
<blockquote>
<p>Find more detail about the key here<br>
<a href="https://docs.fastlane.tools/actions/app_store_connect_api_key/">https://docs.fastlane.tools/actions/app_store_connect_api_key/</a></p>
</blockquote>
<h2 id="withenviromentvariablesfastlane_userandfastlane_apple_application_specific_password">With Enviroment variables <code>FASTLANE_USER</code> and <code>FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD</code></h2>
<p>The second way to upload to App Store is to use lane <code>upload_to_testflight</code>. With this lane, you need to prepare two environment variables as Credentials  for uploading.</p>
<blockquote>
<p>About environment variables: <a href="https://support.apple.com/guide/terminal/use-environment-variables-apd382cc5fa-4f58-4449-b20a-41c53c006f8f/mac">https://support.apple.com/guide/terminal/use-environment-variables-apd382cc5fa-4f58-4449-b20a-41c53c006f8f/mac</a></p>
</blockquote>
<p>Let take a look at an example:</p>
<pre><code>lane :release do
  upload_to_testflight(
    apple_id: &quot;your-app-apple-id&quot;
    skip_submission: true,
    ipa: &quot;#{ipa_path}&quot;,
    skip_waiting_for_build_processing: true,
  )
end
</code></pre>
<p>It's simpler than the first lane, but there's no any user credentials in lane parameters, like <code>.p8</code> key, so to let the script work, we need to find out 2 environment variables: <code>FASTLANE_USER</code> &amp;<code>FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD</code> and set it before running the script.</p>
<ul>
<li><code>FASTANE_USER</code> is your developer id</li>
<li><code>FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD</code> is one of App-Specific Passwords in your apple id managemeng page <a href="https://appleid.apple.com/account/manage">https://appleid.apple.com/account/manage</a></li>
</ul>
<blockquote>
<p>Details can be found: <a href="https://docs.fastlane.tools/best-practices/continuous-integration/#application-specific-passwords">https://docs.fastlane.tools/best-practices/continuous-integration/#application-specific-passwords</a></p>
</blockquote>
<p><img src="http://www.hapq.me/content/images/2022/07/Screen-Shot-2022-07-24-at-23.49.06.png" alt="Screen-Shot-2022-07-24-at-23.49.06"></p>
<ul>
<li><code>apple_id</code> can be found in your app's information page in App Store Connect web page.</li>
</ul>
<p><img src="http://www.hapq.me/content/images/2022/07/Screen-Shot-2022-07-24-at-23.57.20.png" alt="Screen-Shot-2022-07-24-at-23.57.20"></p>
<p>Let's try to see result. If you found any problem please let me know in comment section down below.</p>
<h2 id="conclusion">Conclusion</h2>
<p>The first way seems more complicated than the second way, and you need to be an admin to generate <code>.p8</code> key. But you can control the flow by put the key in any place you want to automate and you don't rely on any user's account.</p>
<p>The second way is base on account, and if you work in an organization the account can be expired if people leave. So it's suitable for personal account.</p>
<p>Thanks a lot for reading. Happy coding.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Bloom Filter by Swift]]></title><description><![CDATA[<!--kg-card-begin: markdown--><h2 id="overview">Overview</h2>
<p>Bloom filter is <strong>Probabilistic data struct</strong>, it can tell an object is <strong>definitely not</strong> exist in the set or <strong>problably exist</strong> in the set.<br>
Means when it tells <code>false</code>, it is the 100% correct, and when it tells <code>true</code> it's not 100%, there's a chance that element doesn't exist</p>]]></description><link>http://www.hapq.me:80/bloom-filter-space-efficient-data-structure-to-check-existence-of-an-element/</link><guid isPermaLink="false">628f2734e67b057a0ba7c7a3</guid><category><![CDATA[algorithm]]></category><category><![CDATA[swift]]></category><category><![CDATA[coding]]></category><category><![CDATA[english]]></category><dc:creator><![CDATA[HàPQ]]></dc:creator><pubDate>Thu, 26 May 2022 07:13:54 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="overview">Overview</h2>
<p>Bloom filter is <strong>Probabilistic data struct</strong>, it can tell an object is <strong>definitely not</strong> exist in the set or <strong>problably exist</strong> in the set.<br>
Means when it tells <code>false</code>, it is the 100% correct, and when it tells <code>true</code> it's not 100%, there's a chance that element doesn't exist in the set.</p>
<p>It's just a filter, and doesn't store actual objects.<br>
So for sets of large number of elements, there's huge performance different between a hash table and a Bloom filter.</p>
<p>The following figure describes how to add an element.</p>
<p><img src="http://www.hapq.me/content/images/2022/05/Screen-Shot-2022-05-13-at-16.35.21.png" alt="Screen-Shot-2022-05-13-at-16.35.21"></p>
<p>A Bloom Filter contains set of Hash functions and a bit array to store results.</p>
<ul>
<li>When inserting an element, we compute all hashes and turn on the certain bit.</li>
<li>When checking existence of an element, we also compute all hashes by hash functions above. Hash results will be used to pick the status of bits.<br>
If any bit is not turned on, the element does not exist.<br>
If all bits are turned on, we can tell the element <strong>might</strong> exist.</li>
</ul>
<p><img src="http://www.hapq.me/content/images/2022/05/Group-28.png" alt="Group-28"></p>
<p>It's easily to figure that bloom filter cannot support deletetion, if we turn off any bit it may affects existence of many elements.</p>
<h2 id="implementation">Implementation</h2>
<p>Here is simple implementation. We support three methods for our Bloom Filter: <code>insert</code>, <code>filter</code>, and <code>isEmpty</code></p>
<pre><code>class BloomFilter&lt;T&gt; {
  private var array: [Bool]
  private var hashes: [(T) -&gt; Int]

  init(size: Int = 1024, hashes: [(T) -&gt; Int]) {
    self.array = [Bool](repeating: false, count: size)
    self.hashes = hashes
  }

  private func computeHashes(_ value: T) -&gt; [Int] {
    return hashes.map { hash in abs(hash(value) % array.count) }
  }
}

extension BloomFilter {
  func insert(_ element: T) {
    for hashValue in computeHashes(element) {
      array[hashValue] = true
    }
  }

  func filter(_ value: T) -&gt; Bool {
    return computeHashes(value)
        .map { hashValue in array[hashValue] }
        .reduce(true, { $0 &amp;&amp; $1 })
  }

  func isEmpty() -&gt; Bool {
    return array.reduce(true) { prev, next in prev &amp;&amp; !next }
  }
}
</code></pre>
<h2 id="hashfunctions">Hash functions</h2>
<p>There's a swift evolution about hash functions.<br>
<a href="https://github.com/apple/swift-evolution/blob/master/proposals/0206-hashable-enhancements.md">https://github.com/apple/swift-evolution/blob/master/proposals/0206-hashable-enhancements.md</a><br>
Hashable protocol was changed from property <code>hashValue:Int</code> to additional <code>hashInto(hasher: Hasher)</code> function</p>
<h3 id="swift10">Swift 1.0</h3>
<pre><code>struct GridPoint {
  var x: Int
  var y: Int
}

extension GridPoint: Hashable {
  var hashValue: Int {
    return x.hashValue ^ y.hashValue &amp;* 16777619
  }

  static func == (lhs: GridPoint, rhs: GridPoint) -&gt; Bool {
    return lhs.x == rhs.x &amp;&amp; lhs.y == rhs.y
  }
}
</code></pre>
<h3 id="swift41">Swift 4.1~</h3>
<pre><code>extension GridPoint: Hashable {
  func hash(into hasher: inout Hasher) {
    hasher.combine(x)
    hasher.combine(y)
  }
}
</code></pre>
<p>We can take advantage of this Hasher to implement Hash functions for our Bloom filter by providing a feed value for each hash. Under the hood of Hasher is SipHash.</p>
<pre><code>func hash(_ valueToHash: T) -&gt; Int where T is Hashable {
  var hasher = Hasher()
  hasher.combine(nthFunctionFeedValue)
  hasher.combine(valueToHash)
  return hasher.finalize()
}
</code></pre>
<p>There's also other hash function like naive or djb2</p>
<pre><code>func naiveHash(_ string: String) -&gt; Int {
  let unicodeScalars = string.unicodeScalars.map { Int($0.value) }
  return unicodeScalars.reduce(0, +)
}

func djb2Hash(_ string: String) -&gt; Int {
  let unicodeScalars = string.unicodeScalars.map { $0.value }
  return unicodeScalars.reduce(5381) {
    ($0 &lt;&lt; 5) &amp;+ $0 &amp;+ Int($1)
  }
}
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[TLV - Tag Length & Value, minimal data format for communicating]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>TLV is basically a multi-levels data format represented by an <code>array of bytes</code>. There're no wasted data like JSON with brackets or spaces, or XML with duplicated tag names.</p>
<p>The data includes 3 parts <code>Tag</code>, <code>Length</code> of <code>Value</code>, and <code>Value</code>.</p>
<p><img src="http://www.hapq.me/content/images/2022/03/tlv.png" alt="tlv"></p>
<p><a href="https://www.it610.com/article/5339878.htm">Read this Article first </a></p>
<h1 id="tagtypeoftlv">Tag - type of TLV</h1>
<p>Depend</p>]]></description><link>http://www.hapq.me:80/tlv-tag-length/</link><guid isPermaLink="false">62270989e67b057a0ba7c6e4</guid><category><![CDATA[swift]]></category><category><![CDATA[data-type]]></category><category><![CDATA[ios]]></category><category><![CDATA[tlv]]></category><category><![CDATA[tag-length-value]]></category><category><![CDATA[english]]></category><category><![CDATA[coding]]></category><dc:creator><![CDATA[HàPQ]]></dc:creator><pubDate>Tue, 08 Mar 2022 07:54:57 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>TLV is basically a multi-levels data format represented by an <code>array of bytes</code>. There're no wasted data like JSON with brackets or spaces, or XML with duplicated tag names.</p>
<p>The data includes 3 parts <code>Tag</code>, <code>Length</code> of <code>Value</code>, and <code>Value</code>.</p>
<p><img src="http://www.hapq.me/content/images/2022/03/tlv.png" alt="tlv"></p>
<p><a href="https://www.it610.com/article/5339878.htm">Read this Article first </a></p>
<h1 id="tagtypeoftlv">Tag - type of TLV</h1>
<p>Depend on system or device, there's a pre-defined Tags list.<br>
EMV smartcards also provide common Tags list, these tags should also be considering.</p>
<blockquote>
<p><a href="https://emvlab.org/emvtags/all/">https://emvlab.org/emvtags/all/</a></p>
</blockquote>
<p>Tag's data type is unsign integer 32bit<code> UInt32</code>, it follows <code>ISO/IEC 8825</code> for BerTLV.</p>
<blockquote>
<p>If all bits from 0 → 5 of first byte is set, we use it with all next bytes that have 7th bit is set.</p>
</blockquote>
<p>Here is pseudo code:</p>
<pre><code>private func getTag(_ stream: InputStream) -&gt; TLV.Tag? {
   guard let first = stream.readByte() else { return nil }
   
   /// check 5 bits of first byte is set
   guard first &amp; 0x1f == 0x1f else { /// = 0001 1111 = 31
       return TLV.Tag(rawValue: UInt32(first))
   }
  
   var result: [UInt8] = []
   /// read until finding a byte that has 7th bit not set
   while let next = stream.readByte() {
       result.append(next)
       if next &amp; 0x80 != 0x80 { /// = 1000 0000 = 128
           break
       }
   }
   result.insert(first, at: 0)
   return TLV.Tag(rawValue: UInt32(result))
}

extension TLV {
    enum Tag: UInt32, CaseIterable {
        case responseData = 0xe1
        case cardStatus = 0x48
        case applicationLabel = 0x50
        ....
    }
}
</code></pre>
<h1 id="lengthlengthofvalue">Length - length of Value</h1>
<p>Because length of <code>Value</code> is dynamic so we need to know how many bytes we should read.</p>
<p>Next TLV starts at the end of current TLV's <code>Value</code>.<br>
For Miura Card Readers the <code>Length</code> is read as following cases of first byte:</p>
<blockquote>
<p>If the 8th bit of first byte is not set, use it as Length<br>
If the 8th bit of first byte is set, unset it and use new value as length of Length</p>
</blockquote>
<pre><code>func getLength(on stream: InputStream) -&gt; TLV.Length {
    let firstByte = stream.readByte()
    guard firstByte &amp; 0x80 == 0x80 else {
        return TLV.Length(firstByte)
    }
    let lenOfLength = max(Int(first &amp; 0x7f), 1)
    let lengthBytes = stream.readBytes(count: lenOfLength)
    return TLV.Length(lengthBytes)
}

extension TLV {
    typealias Length = UInt32
}

public extension UnsignedInteger {
    init(_ bytes: [UInt8]) {
        precondition(bytes.count &lt;= MemoryLayout&lt;Self&gt;.size)
        var value: UInt64 = 0
        for byte in bytes {
            value &lt;&lt;= 8
            value |= UInt64(byte)
        }
        self.init(value)
    }
}
</code></pre>
<h1 id="value">Value</h1>
<p><code>Value</code> has variety types: String, a sub TLV or list of TLVs.<br>
Because <code>Value</code> can contains sub TLVs, so we can consider TLV is Tree or Graph data structure. Some traversal algorithim like Pre-Order, In-Order, Level Order should be implemented to search sub tags.</p>
<p>The properly way to parse TLV is using Recursive or Queue:</p>
<pre><code>let parser = TLVParser()

extension TLV {
	struct Value {
		let tlvs: OrderedSet&lt;TLV&gt;?
		let string: String?

		init(bytes: [UInt8]) {
			let data = Data(bytes)
			let tlvs = parser.parse(data)
			if !tlvs.isEmpty { self.tlvs = tlvs }
			else { self.string = String(data: data, encoding: .utf8) }
		}
	}
}
struct TLVParser {
	func parse(data: Data) -&gt; OrderedSet&lt;TLV&gt; {
        let stream = InputStream(data: data)
    	stream.open()
    	defer { stream.close() }

		var tlvs = OrderedSet&lt;TLV&gt;()
    	while dataStream.hasBytesAvailable {
	 		guard let tag = getTag(on: stream) else { return tlvs }
			let length = getLength(on stream: stream)
    		let value = Value(stream.readBytes(count: length))
			tlvs.append(TLV(tag, length, value))
    	}
        return tlvs
	} 
}
</code></pre>
<p>The result TLVs should be in-order and can be searched using its keys.<br>
In summary, TLV is lightweight but not application developer friendly.<br>
Apple also provide the CryptoTokenKit with TLV, BerTLV parser. But seems not work well with Miura devices because of private Tags.<br>
<a href="https://developer.apple.com/documentation/cryptotokenkit/tktlvrecord">https://developer.apple.com/documentation/cryptotokenkit/tktlvrecord</a></p>
<p>Happy Coding.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[SwiftUI's LocalizedStringKey]]></title><description><![CDATA[<p>When doing localization with UIKit, we need to convert the key to expectation string by using <code>NSLocalizedString</code> method. We also need to reload target views when language changed.</p><pre><code>self.lblTitle = "Untitled".localized</code></pre><p>Things getting easier with SwiftUI. There's new struct was introduced, <code>LocalizedStringKey</code>. We just need to assign correct key</p>]]></description><link>http://www.hapq.me:80/swiftuis-localizedstringkey/</link><guid isPermaLink="false">61aaf021e67b057a0ba7c61e</guid><category><![CDATA[swift]]></category><category><![CDATA[swiftUI]]></category><category><![CDATA[coding]]></category><category><![CDATA[localizable]]></category><dc:creator><![CDATA[HàPQ]]></dc:creator><pubDate>Sun, 12 Dec 2021 15:08:04 GMT</pubDate><content:encoded><![CDATA[<p>When doing localization with UIKit, we need to convert the key to expectation string by using <code>NSLocalizedString</code> method. We also need to reload target views when language changed.</p><pre><code>self.lblTitle = "Untitled".localized</code></pre><p>Things getting easier with SwiftUI. There's new struct was introduced, <code>LocalizedStringKey</code>. We just need to assign correct key to correct label, the rest is on the framework. </p><p>Let take a quicklook at official document.</p><pre><code>https://developer.apple.com/documentation/swiftui/localizedstringkey</code></pre><p><br>They say, when we use the initializer with a string like <code>Text("Hello World")</code>, SwiftUI will convert to a <code>LocalizedStringKey</code> and uses that to lookup the a localization of the <code>Hello World</code> string.</p><p>There're some elements that use string as param for initializer</p><pre><code>Text("Username")

Button("Sign In", action: signIn)

Label("Lightning", systemImage: "bolt.fill")

Link("View Our Terms of Service",
      destination: URL(string: "https://www.example.com/TOS.html")!)

Picker("Flavor", selection: $selectedFlavor) {
    Text("Chocolate").tag(Flavor.chocolate)
    Text("Vanilla").tag(Flavor.vanilla)
    Text("Strawberry").tag(Flavor.strawberry)
}

Toggle("Vibrate on Ring", isOn: $vibrateOnRing)</code></pre><p>They are called <code>stringLiteral</code>. But how about using variable instead of fixed string?</p><pre><code>var signTitle = "Sign In"
Button(signTitle, action: signIn)</code></pre><p>This should not work automatically to prevent unexpected values. To use localized string instead of variable's raw data, we just need to convert it to localized key manually.</p><pre><code>var signTitle = "Sign In"
Button(LocalizedStringKey(signTitle), action: signIn)</code></pre><p>Last but not least, it takes less effort than to UIKit to change language while app is running. A simple line of code to switch <code>Locale</code>, all <code>LocalizedStringKey</code>s, include all child-views, will be reloaded automatically.</p><pre><code>ContentView()
    .environment(\.locale, .init(identifier: "ja"))</code></pre><p>Hope this tips will help you enjoy SwiftUI more.</p>]]></content:encoded></item><item><title><![CDATA[HashCash - crypto currencies' algorithm]]></title><description><![CDATA[<!--kg-card-begin: markdown--><h2 id="introduction">Introduction</h2>
<p>At the moment of writting this post, Bitcoin just hit <code>$60,000</code> with a market cap over <code>1 TRILLION USD</code>. That's huge number.</p>
<p>If you invested <code>250$</code> for <code>17 Bitcoin</code> in <code>2013</code>, now you are billionaire.<br>
I wish I have a time-machine to travel to the past and buy</p>]]></description><link>http://www.hapq.me:80/hashcash-algorithm-to-generate-crypto-currencys-transaction-stamp/</link><guid isPermaLink="false">60ea976e5016800c2ba0448c</guid><category><![CDATA[swift]]></category><category><![CDATA[coding]]></category><category><![CDATA[algorithm]]></category><category><![CDATA[crypto currency]]></category><category><![CDATA[bitcoin]]></category><category><![CDATA[english]]></category><dc:creator><![CDATA[HàPQ]]></dc:creator><pubDate>Sat, 03 Apr 2021 08:21:46 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="introduction">Introduction</h2>
<p>At the moment of writting this post, Bitcoin just hit <code>$60,000</code> with a market cap over <code>1 TRILLION USD</code>. That's huge number.</p>
<p>If you invested <code>250$</code> for <code>17 Bitcoin</code> in <code>2013</code>, now you are billionaire.<br>
I wish I have a time-machine to travel to the past and buy some :cry:</p>
<p>Hmm just get back to life :). As you may know, every Bitcoin transactions block need to be sealed, when someone seal it correctly they will get rewards. So how to generate the Stamp to seal?</p>
<p>Each block of transactions contains <code>header</code> and <code>body</code>. <code>Body</code> is the list of transactions, <code>header</code> is a string which has format:<br>
<code>version:hashPrevblock:hashMerkleRoot:time:bits:nonce</code></p>
<ul>
<li><code>version</code>: block version number</li>
<li><code>hashPrevBlock</code>: 256-bit hash of the previous block header</li>
<li><code>hashMerkleRoot</code>: 256-bit hash of current block body</li>
<li><code>time</code>: current block time stamp, since 1970-01-01T00:00 UTC</li>
<li><code>bits</code>: current target in compact format</li>
<li><code>nonce</code>: 32bit-number start from 0</li>
</ul>
<p>A block is considered as valid one, if <code>SHA256(SHA256(header))</code> passing the verification algorithm - check result string has exact number of leading zeros. Valid header is the Stamp to be mined.</p>
<p>The algorithm to calculate header based on <code>Hashcash</code> algorithm.<br>
Let see how it works.</p>
<h2 id="theinputandtheoutput">The Input and the Output</h2>
<h3 id="input">Input</h3>
<p><code>version</code>: version of block<br>
<code>resource</code>: consider as block information (<code>hashPrevBlock</code> and <code>hashMerkleRoot</code>)<br>
<code>bits</code>: leading zeros padding to validate (default is 20)<br>
<code>ext</code>: additional information (optional)<br>
<code>timestamp</code>: current datetime in format <code>yyyyMMddHHmmss</code><br>
<code>saltLength</code>: length to create a random string to make result unique (default is 16)</p>
<p>the final input:<br>
<code>version</code>:<code>bits</code>:<code>timestamp</code>:<code>resource</code>:<code>ext</code>:<code>saltString</code></p>
<h3 id="output">Output</h3>
<p>Output format should be: <code>version</code>:<code>bits</code>:<code>timestamp</code>:<code>resource</code>:<code>ext</code>:<code>saltString</code>:<code>counter</code></p>
<p><code>counter</code> was made while algorithm running.<br>
It starts from <code>0</code> until valid output was found.</p>
<h2 id="algorithm">Algorithm</h2>
<p>The algorithm is not hard as you though:</p>
<ul>
<li>Build input strings with correct format</li>
<li>Add counter to input</li>
<li>Hash the input</li>
<li>Repeat step 2 and 3 if hashed value is not contains exactly leading zeros</li>
<li>Return result if the loop was breaked.</li>
</ul>
<pre><code>func mint(
    version: Int,
    resource: String,
    bits: Int = 20,
    ext: String = &quot;&quot;,
    timestamp: String,
    saltLength: Int = 16
) -&gt; String {
    let salt = String.salt(length: saltLength)
    let input = &quot;\(version):\(bits):\(timestamp):\(resource):\(ext):\(salt)&quot;
    
    var counter = 0
    while(true) {
        let challenge = input + &quot;:\(String(format: &quot;%02x&quot;, counter))&quot;
        let hashed = challenge.sha256()
        if hashed.hasLeadingZeros(bits) {
            return challenge
        }
        counter += 1
    }
}
</code></pre>
<pre><code>extension String {
    static func salt(length: Int) -&gt; String {
        let chars = &quot;abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/=&quot;
        return (0..&lt;length).map { _ -&gt; String in
            let random = Int.random(in: 0..&lt;chars.count)
            let index = chars.index(chars.startIndex, offsetBy: random)
            return String(chars[index])
        }.joined()
    }
    
    func sha256() -&gt; String {
        let data = self.data(using: .utf8)!
        let hashed = SHA256.hash(data: data)
        return hashed.compactMap { String(format: &quot;%02x&quot;, $0) }
            .joined()
    }
    
    func hasLeadingZeros(_ zerosCount: Int) -&gt; Bool {
        let digits = Int(ceil(Double(zerosCount) / 4))
        let zeros = String(repeating: &quot;0&quot;, count: digits)
        let indexToCheck = self.index(self.startIndex, offsetBy: digits)
        return zeros == self[startIndex..&lt;indexToCheck]
    }
}

extension Date {
    func timestamp() -&gt; String {
        let formatter = DateFormatter()
        formatter.dateFormat = &quot;yyyyMMddHHmmss&quot;
        return formatter.string(from: self)
    }
}
</code></pre>
<h2 id="verifytheseal">Verify the seal</h2>
<p>To verify this seal <code>version</code>:<code>bits</code>:<code>timestamp</code>:<code>resource</code>:<code>ext</code>:<code>saltString</code>:<code>counter</code><br>
we need to do some steps:</p>
<ul>
<li>verify order of elements</li>
<li>check expirations of <code>timestamp</code></li>
<li>verify <code>resource</code> is matching</li>
<li>verify hashed string contains correct leading zeros.</li>
</ul>
<pre><code>protocol Validator {
    var version: Int { get }
    var timeFormat: String { get }
    var expiration: UInt? { get }
    var bits: UInt { get }
    
    func validate(_ stamp: String, _ resource: String?) -&gt; Bool
}

extension Validator {
    func validate(_ stamp: String, _ resource: String?) -&gt; Bool {
        let components = stamp.components(separatedBy: &quot;:&quot;)
        
        guard components.count &gt; 6 else {
            return false
        }
        
        guard checkVersion(components[0]),
              checkBits(components[1]),
              checkDate(components[2]),
              checkResource(components[3], with: resource) else {
            return false
        }
        
        return checkHash(stamp)
    }
    
    private func checkDate(_ date: String) -&gt; Bool {
        let dateformatter = DateFormatter()
        dateformatter.dateFormat = self.timeFormat
        guard let date = dateformatter.date(from: date) else {
            return false
        }
        
        if let expiration = self.expiration {
            let expDate = Date(timeIntervalSinceNow: -TimeInterval(expiration))
            guard (date &gt;= expDate) else {
                return false
            }
        }
        
        return true
    }
    
    private func checkVersion(_ ver: String) -&gt; Bool {
        return (Int(ver) ?? 1) &lt;= self.version
    }
    
    private func checkResource(_ res: String, with res2: String?) -&gt; Bool {
        if res2 == nil {
            return true
        }
        return res2 == res
    }
    
    private func checkBits(_ bits: String) -&gt; Bool {
        return Int(bits) == Int(self.bits)
    }
    
    private func checkHash(_ string: String) -&gt; Bool {
        return string.sha256().hasLeadingZeros(bits)
    }
}
</code></pre>
<p>let give it a try</p>
<pre><code>let stamp = mint(version: 1, resource: &quot;hallo&quot;, timestamp: Date().timestamp())
print(stamp)
print(Ver1Validator().validate(stamp, &quot;hallo&quot;))
</code></pre>
<p>this is a sample valid result i tried on my machine:</p>
<blockquote>
<p>1:20:20210403170942:hallo::MoxqvFOLGnwjs=Ko:8717<br>
with hashed value<br>
000005219105fb754ab98d4715e23fd2b489823b0468c337db7121c6432bbe9e</p>
</blockquote>
<h2 id="conclusion">Conclusion</h2>
<p>Finally we did it... That's good start to deep understand how crypto currency works.</p>
<p>But this algorithm is just a tiny part of crypto currency.<br>
There're much more difficulty in real world like: tool to create transactions, to to distribute the whole block-chain to validators (miner) or infrastructure for system...</p>
<p>Let see more in near future.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>