Open Source Contribution - Enhancing Functionality with Appwrite
I. Introduction ✌️
Over the last eight weeks, I have had the valuable opportunity to contribute to the open-source project Appwrite. Through active participation in resolving various issues, including image compression and text-to-speech synthesis, I have gained a firsthand understanding of the critical role that well-organized and clean code plays in a collaborative software development setting. As I look back on my journey, I aim to share how my partner and I successfully solved the issues, submitted two pull requests, and the lessons I have learned from this experience.
II. What is Appwrite? 🤔
Appwrite is a comprehensive backend server designed for various applications, including Web, Mobile, Native, or Backend platforms. This solution is structured as a collection of Docker microservices. Leveraging Appwrite, developers can seamlessly incorporate their applications with a database to manage user and team data, facilitate user authentication through multiple sign-in methods, oversee storage and file handling, employ Cloud Functions, and access other services.
For instance, let's consider the scenario of developers working on an audiobook application. While implementing the functionality to convert a book into speech, the development team aims to offer diverse voices from different providers like Google Cloud, Amazon Web Services, and Microsoft Azure. Since each provider adheres to distinct coding styles, developers must consult each provider's documentation to successfully integrate their respective APIs into the application. However, with the aid of Appwrite, all APIs come pre-integrated, eradicating the need for an exhaustive exploration of individual provider documentation. Developers only need to refer to Appwrite's documentation to access a multitude of APIs from various providers worldwide.
It's noteworthy that Appwrite has recently achieved a significant milestone by reaching the benchmark of 1 million connected users. 🎉🎉🎉
III. What are our issues?⚡️
During the past eight weeks, our team worked on two issues called compressedImage and textToSpeech. These issues are essential since it adds more functions that software developers can use with Appwrite.
The user initiates the sequence by entering a curl command, which includes variables and payloads related to the task. This specific curl command sends a JSON request, subsequently transmitted to the Runtime Container through a designated port. The pivotal repository for our function, acts as an intermediary facilitating exchanges of requests and responses with the designated providers. After completing the function's task, the processed data is returned to the user as a JSON response.
IV. CompressImage 🖼️
Pull Request: Feat 4122 Compress Image Python
First, the user will provide the request's variables (name of the provider and key) and payload (the image in based64 format).
curl http://localhost:3000/ -H "X-Internal-Challenge: secret-key" -H "Content-Type: application/json" -d '{"payload": {"provider": "tinypng", "image":"iVBORw0KGgoAAAANSUhEUgAAABIAAAAUCAYAAACAl21KAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAJ4SURBVDhPzZTPTxNBFMe/O1t2WbstttgqCOFHQ0BjIGKCjTESLiYYQzjpSUg8aLxi4sET3pp46t2EcNB4qX+AXiQ9NGL8QfwBalJBJRRKV6BbtvvbYXYlNvamBz7JZN93983bN/PeDOdS8B8g/vOf2c9Im5tDdX4erq4j0N0NeWwMRJaZ05NXReTyW8zuPy7j2rkWZv8JC7QxNQVjaQlOuQzQuESSQCIRxNNpXHpUwKZqoFy12YSgQBAPi3h6+zTTv+HvDA9Pq5kM7GIRME02XE2DoyhYLPOYLchQTA6m7bJRMRxslA3EQwIG2kN+GLpHWi7HJtajurCARsfwVS3Zzz99y4OwII7jy1oaqhXqUP/blmb5lgcREglAEHxZy49YFDovgKy9gbz4ECe+3MdoKYXroce4HPvke3lwlqK4hYkJWKur/iuPfHsQDy60IptxIFZWIFSLtFdsRA+56Iy6SE+2Quodh3j2LvPn76VS01IyCY5WyuCBkmDjddzC7JCIj8USjNwSbLpEw3KhWxxdEodlhSDRuIJEcAOk+SRIuMNryAbaN2GaVeHWVcyMt2HmfBBfYwFoH3bY3+rxPC/BqazDLrxger+zedo36pEQVmQHmzKBw9GWUr3eqcfmLk3f1mlpverVHJGwGEKzFPUVDR6rX4Q9emMmODEM0tTFdE2gnkgXBo+e8hUgDkV8q5aBYzqunCEIdFxEoGuUvfvr9H/fWcWz5Sxerr1FiaatvlPAvd+GVSjB0lS0HbZxcySEweQIGvpvgI/2sXl1r5FtvYw1upG7poYARzd9XQWpVGFufUOTxKGvpxNcsGV/WXscvPvooAUCfgH9Pv4iODys+QAAAABJRU5ErkJggg=="}, "variables": {"API_KEY": "<YOUR API_KEY>"}}'
All of this information will be sent out into the Main function. Next, the Main function will call Validate Credentials and extract the necessary information for the provider. If the credentials are valid, the main will upload the image to one of the providers (KrakenIO or TinyPNG) to compress. Finally, The user will receive their compressed images based64 formats in a JSON response.
iVBORw0KGgoAAAANSUhEUgAAABIAAAAUCAMAAAC3SZ14AAABLFBMVEX///8eHh7gMTEZccIvnkTiOjrw+PL87u6ixeb+8uF0qtrM6NJFjM4wf8gkeMX85MT2v78mbbkuarPujY3thITkSkrjRETiPT1AMyFeRB8rJh7ymR398/OtzOr75ubi8eT63d3X7dv87dfR6dZln9VVltHI0sq94MTKx8LJzLehq6qd0afUwKXxpaXFt6Txn5+SzJ360psbWJHEfo74y4v4yIXrfHz4w3pouHf3v3FgtXHpcXF7fmr2umclSGdVsGalXGZsoGHhY2BNrF+8T19ajFblVVVJg1TBR1QdOVRCTE1iXkpKV0XNUEUxnURDkkJSiEAsiT6+TjwsdTikVjfvoTUkLjVGPzLzojHFhzAlUy0jSioiPidNNhdVOxbxlBPSgxNpRRN8UBGpZwp9EYrKAAAAs0lEQVQY063KRXYCQBAA0UkyBHd3d3d3d3eH+9+B1z0cgAW1/K/Ip8mUqrCJELNAwHtLUa3laDoWkVgiEYvYo/mFkvI/CD8VB6mlQHIBZRj1GLmBolqkmjxRGQxLASCuGmTR7m8MBp1+nQazKRvj1Wz5gz13Uvz89enkyOhuTBHI26T0xOhyzSIJC5QeGD2McaT/GKVzlPOtzCeYNZirdkdbnX6fdyDAZxc6Pb5ISArPN3sB8REVVO/DWBYAAAAASUVORK5CYII=
Here is the result after converting based64 format into image.
You can try converting them on your own using this website.
Recommended by LinkedIn
V. TextToSpeech🗣️
Pull Request: Feat 4155 Text to Speech Python
First, the user will provide the request's variables (name of the provider, language code and key) and payload (the text).
curl http://localhost:3000/ -H "X-Internal-Challenge: secret-key" -H "Content-Type: application/json" -d '{"payload": {"provider": "aws", "language":"en-US", "text":"Hello World!"}, "variables": {"API_KEY": "<YOUR_API_KEY>", "SECRET_API_KEY": "<YOUR_SECRET_API_KEY>"}}'
All of this information will be sent out into the Main function. Next, the Main function will call Validate Credentials and extract the necessary information for the provider. If the credentials are valid, the main will upload the text to one of the providers (Google Cloud, Microsoft Azure, or Amazon Web Service) for speech synthesis. Finally, The user will receive their synthesized speech as based64 formats in a JSON response.
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
You can paste the whole speech in based64 format above into this website to here the actual sound.
VI. Challenges and Lessons
1.Interfaces
While addressing the initial issue, my partner and I engaged in iterative revisions of the 'main.py' file. We realized that during the creation of 'unittest.py', the code in 'main.py' proved to be considerably challenging to test and lacked coverage for all the intended test cases. Consequently, as we tackled the second issue, we established clear interfaces for both 'main.py' and 'testmain.py', seeking input from our mentor for refinement. Once we reached a consensus on the framework, implementing the functions became notably swift and straightforward.
It is consistently more efficient and less time-consuming to rectify issues within the interfaces rather than within fully developed code. Hence, it is advisable to draft the interfaces before initiating the implementation phase. Throughout the interface drafting, it is prudent to maintain a proactive mindset, considering potential test cases that will later aid in comprehensive testing.
➡️Write interfaces before implementing functions.
➡️Test-driven development is great.
2.GitHub conflicts
While working on this issue, my partner and I had our branches, and we had to push our code and pull from the other multiple times. We always have merge conflicts that are hard to resolve. Our mentor had shown us how to use ‘git rebase’ and ‘git squash’ to solve merge conflicts, but more importantly is how to get fewer conflicts. The tip has another branch called the main branch. When each of us pulled and pushed, instead of pulling and pushing from each other branch, we pulled and pushed from the main branch. Using this way, the main branch looks much cleaner with fewer commits (since squashing all commits before merging our code into the main branch) and, as a result, fewer conflicts.
➡️Everyone in a team should pull and push from a main branch.
➡️Always squashing all commits before merging
3. API keys
There are multiple ways to keep API keys safe. In this project, we added all keys into a secret.py file and included this file in .gitignore. Another method can be to export it as a variable in the terminal. The critical thing here is to pick at least one way and always keep the API keys safe.
➡️ALWAYS keep the API keys safe.
Thanks Noah Jacinto for being an amazing first reader and helpful feedback!
Software Developer @ Strawbridge Studios
1yHello Ngoc - we worked on Appwrite too, so it was great to see the work of others who also contributed to Appwrite! Great job adding two new, impactful cloud functions!