Skip to content

Commit

Permalink
updated readme and docs
Browse files Browse the repository at this point in the history
  • Loading branch information
christianmat committed Jan 4, 2024
1 parent c6d6be8 commit a496280
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 60 deletions.
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
[![npm version](https://img.shields.io/npm/v/react-native-onboard)](https://www.npmjs.com/package/react-native-onboard)
[![npm version](https://github.com/FrigadeHQ/react-native-onboard/actions/workflows/tests.yml/badge.svg)](https://github.com/FrigadeHQ/react-native-onboard/actions/workflows/tests.yml)
[![npm license](https://img.shields.io/npm/l/react-native-onboard)](https://www.npmjs.com/package/react-native-onboard)
[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier)


<H3 align="center"><strong>centralStorage</strong></H3>
<div align="center">centralStorage is an open source library and server with the same API as localStorage that allows you to store data in a centralized storage.</div>
<br />
Expand All @@ -11,3 +17,43 @@
<a href="https://docs.frigade.com">Docs</a></div>

<br />

## Why
Storing data in localStorage is great, but it's not a good solution for storing data that needs to be shared across multiple devices for the same user. For example, if you want to store whether a user has seen the announcement of a new feature, you can't do that with localStorage because it's only available on the device that the user is currently using.

You could spin up a database, but that's a lot of work for storing simple impression events, and it's not very scalable to always involve backend engineering to store the state of a simple binary flag.

That's where centralStorage comes in. It's a simple, open source library and server that allows you to store data in a centralized storage.

## Features

- 🔧 Simple API (same as localStorage)
- 🚀 Works with all Javascript frameworks
- 📦 Lightweight (~2 kB)
- ✨ Open source and self-hostable
- 🍦 Free community server


## Quick start

Install the library:

```bash
npm install central-storage
```

Import the library:

```javascript
import { CentralStorage } from 'central-storage'


const centralStorage = new CentralStorage('https://central-storage.frigade.com')


const hasSeenModals = await centralStorage.getItem('hasSeenModals')

if (!hasSeenModals) {
await centralStorage.setItem('hasSeenModals', true)
}
```
14 changes: 4 additions & 10 deletions apps/server/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
FROM node:18.13.0-bullseye As build

RUN apt-get update && apt-get install -y openssl
RUN npm install -g pnpm

# Create app directory
WORKDIR /app
Expand All @@ -14,19 +15,12 @@ WORKDIR /app
# Copying this first prevents re-running pnpm install on every code change.
COPY --chown=node:node ./package*.json ./

# Install app dependencies using the `pnpm ci` command instead of `pnpm install`
RUN pnpm ci
RUN pnpm install

# Bundle app source
COPY --chown=node:node . .
RUN mkdir -p /app/certs
RUN openssl genrsa -des3 -passout pass:x -out /app/certs/server.pass.key 2048 && \
openssl rsa -passin pass:x -in /app/certs/server.pass.key -out /app/certs/server.key && \
rm /app/certs/server.pass.key && \
openssl req -new -key /app/certs/server.key -out /app/certs/server.csr \
-subj "/C=US/ST=CA/L=SF/O=Frigade/OU=IT Department/CN=frigade.com" && \
openssl x509 -req -days 365 -in /app/certs/server.csr -signkey /app/certs/server.key -out /app/certs/server.crt && \
chown -R node:node /app/certs
# Optionally add your own certs to use https
RUN chown -R node:node /app/certs

# Use the node user from the image (instead of the root user)
USER node
Expand Down
15 changes: 8 additions & 7 deletions apps/server/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,16 @@ services:
target: build
env_file:
- .env
environment:
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:6432/${POSTGRES_DB}?schema=public
DATABASE_READER_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:6432/${POSTGRES_DB}?schema=public
volumes:
- ./:/app
- /app/node_modules
- /app/certs
- /app/schemas
# Run in dev Mode: npm run start:dev
command: npm run start:dev
command: pnpm run start
ports:
- '4000:4000'
depends_on:
- postgres
- redis
networks:
- app-network
restart: unless-stopped
Expand All @@ -34,13 +30,18 @@ services:
networks:
- app-network
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- redis_data:/data
env_file:
- .env
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
volumes:
docker-nest-postgres:
nest:
redis_data:

networks:
app-network:
20 changes: 5 additions & 15 deletions apps/server/src/entities/entities.controller.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,14 @@
import {
BadRequestException,
Controller,
Get,
Param,
Request,
UseInterceptors,
} from '@nestjs/common'
import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'
import { EntitiesService } from './entities.service'
import {BadRequestException, Controller, Get, Param, Request,} from '@nestjs/common'
import {ApiBearerAuth, ApiOperation, ApiResponse, ApiTags} from '@nestjs/swagger'
import {EntitiesService} from './entities.service'

import { NotFoundInterceptor } from '../middlewares/not-found.interceptor'

import { Actor, Entity } from './entities.interface'
import { HEADER_GLOBAL_STORAGE_INSTANCE_ID, HEADER_GLOBAL_STORAGE_USER_ID } from 'central-storage'
import {Actor, Entity} from './entities.interface'
import {HEADER_GLOBAL_STORAGE_INSTANCE_ID, HEADER_GLOBAL_STORAGE_USER_ID} from 'central-storage'

const publicApiPrefix = '/entities/'

@ApiBearerAuth()
@ApiTags('entities')
@UseInterceptors(NotFoundInterceptor)
@Controller()
export class EntitiesController {
constructor(private readonly entitiesService: EntitiesService) {}
Expand Down
27 changes: 0 additions & 27 deletions apps/server/src/middlewares/not-found.interceptor.ts

This file was deleted.

2 changes: 1 addition & 1 deletion apps/server/swagger-spec.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"openapi":"3.0.0","paths":{"/":{"get":{"operationId":"AppController_root","parameters":[],"responses":{"200":{"description":""}}}},"/v1/me":{"get":{"operationId":"AppController_me","parameters":[{"name":"resyncOrganization","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}}}},"/v1/apiKeys":{"get":{"operationId":"ApiKeysController_findAllByCustomer","summary":"Get all apiKeys for the signed in user","parameters":[],"responses":{"200":{"description":"Return all apiKeys."}},"tags":["apiKeys"],"security":[{"bearer":[]}]}},"/v1/public/flows/{slug}":{"get":{"operationId":"FlowsController_getBySlug","summary":"Get a flow by slug","parameters":[],"responses":{"200":{"description":"The flow has been successfully returned."},"404":{"description":"The flow was not found."}},"tags":["flows"],"security":[{"bearer":[]}]}},"/v1/public/flows":{"get":{"operationId":"FlowsController_find","summary":"Get all public flows for a given application","parameters":[],"responses":{"200":{"description":"Return all flows."}},"tags":["flows"],"security":[{"bearer":[]}]}},"/v1/flows/{id}/versions":{"get":{"operationId":"FlowsController_getFlowVersions","summary":"Get all versions of a flow by id","parameters":[],"responses":{"200":{"description":"The flows have been successfully returned."},"404":{"description":"The flows were not found."}},"tags":["flows"],"security":[{"bearer":[]}]},"post":{"operationId":"FlowsController_createFlowVersion","summary":"Adds a new flow for the signed in user","parameters":[],"responses":{"201":{"description":""}},"tags":["flows"],"security":[{"bearer":[]}]}},"/v1/flows/{id}":{"get":{"operationId":"FlowsController_get","summary":"Get a flow by id","parameters":[{"name":"version","required":true,"in":"query","schema":{"type":"string"}},{"name":"startDate","required":true,"in":"query","schema":{"type":"string"}},{"name":"endDate","required":true,"in":"query","schema":{"type":"string"}},{"name":"includeTimeSeriesStats","required":true,"in":"query","schema":{"type":"string"}},{"name":"timeZoneOffset","required":true,"in":"query","schema":{"type":"string"}},{"name":"forceStatsRefresh","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The flow has been successfully returned."},"404":{"description":"The flow was not found."}},"tags":["flows"],"security":[{"bearer":[]}]},"delete":{"operationId":"FlowsController_delete","summary":"Delete flow","parameters":[],"responses":{"204":{"description":"The flow has been successfully deleted."},"403":{"description":"Forbidden."}},"tags":["flows"],"security":[{"bearer":[]}]},"put":{"operationId":"FlowsController_update","summary":"Update flow","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateFlowDto"}}}},"responses":{"200":{"description":"The flow has been successfully updated."},"403":{"description":"Forbidden."}},"tags":["flows"],"security":[{"bearer":[]}]}},"/v1/flows":{"get":{"operationId":"FlowsController_findAllByCustomer","summary":"Get all flows for the signed in user","parameters":[],"responses":{"200":{"description":"Return all flows."}},"tags":["flows"],"security":[{"bearer":[]}]},"post":{"operationId":"FlowsController_post","summary":"Adds a new flow for the signed in user","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateFlowDto"}}}},"responses":{"201":{"description":""}},"tags":["flows"],"security":[{"bearer":[]}]}},"/v1/flows/{id}/activate":{"put":{"operationId":"FlowsController_activateDraft","summary":"Set selected flow version as active","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivateFlowDto"}}}},"responses":{"201":{"description":"The flow has been successfully been activated."},"403":{"description":"Forbidden."}},"tags":["flows"],"security":[{"bearer":[]}]}},"/v1/webhooks/clerk":{"post":{"operationId":"WebhooksController_create","parameters":[],"responses":{"201":{"description":""}}}},"/v1/public/userGroups":{"post":{"operationId":"UserGroupsController_postPublic","summary":"Adds a new property or events for a given user group","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddPropertyOrEventsToUserGroupDTO"}}}},"responses":{"201":{"description":"The property has been successfully created or updated."}},"tags":["userGroups"],"security":[{"bearer":[]}]}},"/v1/public/flowResponses":{"post":{"operationId":"FlowResponsesController_post","summary":"Adds a new flowResponse for the signed in flowResponse","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateFlowResponseDTO"}}}},"responses":{"201":{"description":""}},"tags":["flowResponses"],"security":[{"bearer":[]}]}},"/v1/flowResponses/export/{flowSlug}":{"get":{"operationId":"FlowResponsesController_get","summary":"Downloads all flowresponses as a CSV","parameters":[{"name":"flowSlug","required":true,"in":"path","schema":{"type":"string"}},{"name":"version","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["flowResponses"],"security":[{"bearer":[]}]}},"/v1/public/users":{"post":{"operationId":"UsersController_postPublic","summary":"Adds a new property or events for a given user","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddPropertyOrEventsToUserDTO"}}}},"responses":{"201":{"description":"The property has been successfully created or updated."}},"tags":["users"],"security":[{"bearer":[]}]}},"/v1/users":{"get":{"operationId":"UsersController_get","summary":"Get a user by foreign id","parameters":[{"name":"foreignId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The user has been successfully returned."},"404":{"description":"The user was not found."}},"tags":["users"],"security":[{"bearer":[]}]},"delete":{"operationId":"UsersController_delete","summary":"Delete a user by foreign id","parameters":[{"name":"foreignId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The user has been successfully deleted."},"404":{"description":"The user was not found."}},"tags":["users"],"security":[{"bearer":[]}]}},"/v1/public/userFlowStates/{flowSlug}":{"get":{"operationId":"UserFlowStatesController_getByFlowSlug","summary":"Get the state of a user in a flow","parameters":[{"name":"foreignUserId","required":true,"in":"query","schema":{"type":"string"}},{"name":"foreignUserGroupId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The flow state was found"},"404":{"description":"The flow or user was not found."}},"tags":["userFlowStates"],"security":[{"bearer":[]}]}},"/v1/public/userFlowStates":{"get":{"operationId":"UserFlowStatesController_getAllForUser","summary":"Get the state of a user in a flow","parameters":[{"name":"foreignUserId","required":true,"in":"query","schema":{"type":"string"}},{"name":"foreignUserGroupId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The flow state was found"},"404":{"description":"The flow or user was not found."}},"tags":["userFlowStates"],"security":[{"bearer":[]}]}},"/v1/userFlowStates/{flowSlug}/{userSlug}":{"get":{"operationId":"UserFlowStatesController_getPrivate","summary":"Get the state of a user in a flow","parameters":[],"responses":{"200":{"description":"The flow state was found"},"404":{"description":"The flow or user was not found."}},"tags":["userFlowStates"],"security":[{"bearer":[]}]},"delete":{"operationId":"UserFlowStatesController_deleteUserFlowState","summary":"Delete the state of a user in a flow","parameters":[],"responses":{"201":{"description":"The flow state was deleted"},"404":{"description":"The flow or user was not found."}},"tags":["userFlowStates"],"security":[{"bearer":[]}]}},"/v1/userFlowStates":{"get":{"operationId":"UserFlowStatesController_getAllUserFlowStates","summary":"Get the state of a user in all flows","parameters":[{"name":"userQuery","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The flow state(s) were found"},"404":{"description":"The user was not found."}},"tags":["userFlowStates"],"security":[{"bearer":[]}]}},"/v1/thirdParty/digitalocean/resources":{"post":{"operationId":"DigitalOceanController_processRequest","parameters":[],"responses":{"201":{"description":""}},"tags":["apiKeys"],"security":[{"bearer":[]}]}},"/v1/thirdParty/digitalocean/sso":{"post":{"operationId":"DigitalOceanController_processSSO","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DigitalOceanSSODTO"}}}},"responses":{"201":{"description":""}},"tags":["apiKeys"],"security":[{"bearer":[]}]}},"/v1/thirdParty/cdp/segment":{"post":{"operationId":"SegmentController_processRequest","parameters":[],"responses":{"201":{"description":""}},"tags":["segment"],"security":[{"bearer":[]}]}}},"info":{"title":"Frigade API","description":"Official Frigade API documentation","version":"1.0","contact":{}},"tags":[],"servers":[],"components":{"schemas":{"CreateFlowDto":{"type":"object","properties":{}},"ActivateFlowDto":{"type":"object","properties":{}},"UpdateFlowDto":{"type":"object","properties":{}},"AddPropertyOrEventsToUserGroupDTO":{"type":"object","properties":{}},"CreateFlowResponseDTO":{"type":"object","properties":{}},"AddPropertyOrEventsToUserDTO":{"type":"object","properties":{}},"DigitalOceanSSODTO":{"type":"object","properties":{}}}}}
{"openapi":"3.0.0","paths":{"/":{"get":{"operationId":"AppController_root","parameters":[],"responses":{"200":{"description":""}}}},"/entities/{key}":{"get":{"operationId":"EntitiesController_get","summary":"Get a entity by key","parameters":[],"responses":{"200":{"description":"The entity has been successfully returned."},"404":{"description":"The entity was not found."}},"tags":["entities"],"security":[{"bearer":[]}]}}},"info":{"title":"centralStorage API","description":"","version":"1.0","contact":{}},"tags":[],"servers":[],"components":{"schemas":{}}}

0 comments on commit a496280

Please sign in to comment.