An online auctions website
- GithubCode
- StackSvelte ,Typescript,Bootstrap,Firebase
E-actions is the first project I developed using svelte. I have been very impressed by Svelte for several reasons:
- It simplifies components definition as well as state management for building complex UIs.
- It provides tools to manage app wide state more effectively (ie context, writable and readable stores)
- Unlike other framworks (React included) it offers more features to build large scale project without relying on other libraries (ie for SSR, SSG and routing).
- The only drawback of Svelte is that it is not very popular yet, so there are fewer blog posts and libraries compared to React.
I used Firestore as serverless backend for this project, I already knew Firestore, but this project allowed me to learn it more into depth, mastering the following features:
- Complex queries to build fuzzy search and category based search for items.
- The onSnapshot API to implement real time offers.
- User authentication using the built-in strategies for Github and Google OAuth.
- How to secure access to protected resources on Firestore using security rules. Security rules are the way Firestore enables developers to build a custom backends by writing code which is executed before a query is processed. It is usefull to validate request data and to check if the user is allowed to execute the query.
This is the code which I wrote to validate every requeste which arrives to Firestore to check if the user has permissions to execute it and if the data passed is valid.
service cloud.firestore { match /databases/{database}/documents { function isUserAuthenticated() { return request.auth != null; } function isUserItemCreator(itemCreatorId) { return request.auth.uid == itemCreatorId; } function doesUserOwnResource() { return isUserAuthenticated() && request.auth.uid == resource.data.userId; } match /questions/{document=**} { function isValidQuestion(data) { return data.text is string && data.text.size() > 0; } function isValidTextUpdate(data) { return doesUserOwnResource() && isValidQuestion(data); } function isValidAnswerUpdate(data) { return isUserItemCreator(data.itemCreatorId) && isValidQuestion(data) && data.answer is string; } allow delete: if isUserAuthenticated() && doesUserOwnResource(); allow create: if isUserAuthenticated() && isValidQuestion(request.resource.data); allow update: if isUserAuthenticated() && (isValidTextUpdate(request.resource.data) || isValidAnswerUpdate(request.resource.data)); allow read: if true; } match /items/{document=**} { function isValidExternalUpdate(data) { return data.views is number || data.questions is list; } function areItemFieldsValid(data) { return ( data.name is string && data.name.size() > 0 && data.nameLowerCase is string && data.nameLowerCase.size() > 0 && data.description is string && data.description.size() > 0 && data.minPrice is number && data.minPrice > 0 && data.categories is list && data.categories.size() > 0 && data.views == request.resource.data.views ); } function isViewsInizialiedToZero(data) { return data.views is number && data.views == 0; } allow create: if true; allow create: if isUserAuthenticated() && isViewsInizialiedToZero(request.resource.data) && areItemFieldsValid(request.resource.data); allow update: if isValidExternalUpdate(request.resource.data) || (doesUserOwnResource() && areItemFieldsValid(request.resource.data)); allow delete: if doesUserOwnResource(); allow read: if true; } match /user-offers/{document=**} { allow create: if isUserAuthenticated(); allow read: if true; } } }