Install the required prerequisites and then follow the steps below.
You'll need:
- Postgres for the database
- NATS JetStream for relaying reliable messages
- Node.js to run the project
Run the following commands:
git clone https://github.com/smooth-sail/smoothsail-manager.git
cd smoothsail-manager
npm install
Create a .env
file with the following:
PORT=3000
PGUSER=<username>
PGHOST=localhost
PGDATABASE=<ffdatabasename>
SDKKEYSDB=<sdkkeysdatabasename>
PGPASSWORD=<yourpassword>
PGPORT=5432
SECRET_KEY=<yoursecret> (Must be 32 chars)
NATS_SERVER="nats://localhost:<portnumber>"
LOGLEVEL=info
NODE_ENV=dev
PORT
is the port the Manager will be running onSDKKEYSDB
andPGDATABASE
should be the names of databases created on Postgres- replace anything that has
<>
with your own credentials - the
SECRET_KEY
will also need to be used for the SmoothSail SDK Service - if PGPORT is not set it will use default value of 5432
- PGPASSWORD does not have to be set if you don't have a password
- LOGLEVEL desired log level (examples:
verbose
,info
,warn
). If not specified, defaults towarn
- NODE_ENV - set to
test
before running tests
Before running the Node.js application, it's essential to start the NATS server as a separate service. The NATS server is responsible for message routing and communication. :
nats-server -js -p <portnumber>
From the root of smoothsail-manager
run the following command to start the manager:
npm run dev
The SmoothSail dashboard will be available at http://localhost:3000
(unless default port was changed)
You'll need the SDK Service in order to communicate feature flag data to consumer applications. If you haven't yet head on over there to get that running.
GET /key
Returns on success:
Status code: 200
{
payload: sdkKey;
}
If no key exists in the database, it will create a new valid key.
Returns on error:
Status codes: 500
{
error: error_message;
}
POST /key
Caution
Invalidates any existing key
Expects:
no body
Returns on success: returns a new valid key.
Status code: 200
{
payload: sdkKey;
}
Returns on error:
Status codes: 500
{
error: error_message;
}
{
fKey: string,
title: string,
description: string,
isActive: boolean,
createdAt: dateTime,
updatedAt: dateTime,
}
GET /api/flags
Expects:
no body.
Returns on success:
Status code: 200
{ payload: [ flag1, flag2, flag3, … ] } // ASC order by date created
Returns on error:
Status codes: 500
{
error: error_message;
}
GET /api/flags/:fKey
Expects:
no body.
fKey
⇒ feature flag key (an HTTP path parameter)
Returns on success:
Status code: 200
{
payload: flag;
}
Returns on error:
Status codes: 404
, 500
{
error: error_message;
}
POST /api/flags
Expects:
{
fKey: fKey,
title: title,
description: description
}
Returns on success:
Status code: 201
{
payload: flag;
}
Returns on error:
Status codes: 400
, 409
, 500
{
error: error_message;
}
DELETE /api/flags/:fKey
Expects:
no body.
fKey
⇒ feature flag key (an HTTP path parameter)
Returns on success:
Status code: 200
{
message: "Flag successfully deleted.";
}
Returns on error:
Status codes: 404
, 500
{
error: error_message;
}
PATCH /api/flags/:fKey
Expects:
fKey
⇒ key of the feature flag (an HTTP path parameter)
body:
{
action: action,
payload: payload
}
Supported action types and expected payloads for that action type:
body update
⇒{ title, description }
toggle
⇒{ isActive: bool }
segment add
⇒{ sKey }
segment remove
⇒{ sKey }
where isActive
is true
or false
(desired toggled stated).
Returns on success:
Status code: 200
Reply body for each action type:
body update
{
payload: flag;
}
toggle
{
payload: flag;
}
segment add
{
payload: segment;
}
segment remove
{
payload: {
message: "Segment was successfully removed.";
}
}
Returns on error:
Status codes: 400
, 404
, 500
{
error: error_message;
}
{
sKey: string,
title: string,
description: string,
rulesOperator: string,
rules: [
{
rKey: integer,
aKey: string,
type: 'boolean' || 'string' || 'number',
operator: string,
value: bool || string || number,
},
]
}
GET /api/segments
Expects:
no body.
query params supported:
Possible query params: fKey
to filter for a given flag. Example: /api/segments?fKey=flag-1
Returns on success:
Status code: 200
{ payload: [ seg1, seg2, seg3, … ] } // sorted by sKey value
Returns on error:
Status codes: 404
, 500
{
error: error_message;
}
GET /api/segments/:sKey
Returns on success:
Status code: 200
{
payload: segment;
}
Returns on error:
Status codes: 404
, 500
{
error: error_message;
}
POST /api/segments
Expects:
{
sKey: sKey,
title: title,
description: description,
rulesOperator: rulesOperator
}
description
is optional
rulesOperator
is optional (default will be used, default value is all
)
title
must be unique value
Returns on success:
Status code: 200
{
payload: segment;
}
Note
Returns segment with empty rules array. To add rules, you need to create a segment first and then add a rule separately (see below)
Returns on error:
Status codes: 400
, 409
, 500
{
error: error_message;
}
DELETE /api/segments/:sKey
Expects:
no body.
sKey
⇒ key of the segment (an HTTP path parameter)
Returns on success:
Status code: 200
{
message: "Segment successfully deleted.";
}
Returns on error:
Status codes: 404
, 409
, 500
{
error: error_message;
}
PATCH /api/segments/:sKey
Expects:
sKey ⇒ key of the feature flag (an HTTP path parameter)
body:
{
action: action,
payload: payload
}
Supported action types and expected request body for that action type:
body update
⇒{ title, description, rulesOperator }
rule add
⇒{ aKey, operator, value }
rule remove
⇒{ rKey }
rule update
⇒{ operator, value, aKey, rKey }
Returns on success:
Status code: 200
Reply body for each action type:
body update
{
payload: segment;
}
Note
returned segment does not contain attribute rules
rule add
{
payload: {
aKey: aKey,
rKey: rKey,
sKey: sKey,
type: type,
operator: operator,
value: value
}
}
rule remove
{ "message": "Rule successfully deleted." }
rule update
{ payload: {
rKey: rKey,
aKey: aKey,
operator: operator,
value: value,
type: type,
sKey: sKey
}
}
Returns on error:
Status codes: 400
, 404
, 409
, 500
{
error: error_message;
}
{
aKey: string,
name: string,
type: 'boolean' || 'string' || 'number',
}
GET /api/attributes
Expects:
no body.
Returns on success:
Status code: 200
{ payload: [ attr1, attr2, attr3, … ] } // ASC order by date created
Returns on error:
Status codes: 500
{
error: error_message;
}
GET /api/attributes/:aKey
Expects:
no body.
aKey
⇒ key of the attribute (an HTTP path parameter)
Returns on success:
Status code: 200
{
payload: attribute;
}
Returns on error:
Status codes: 404
, 500
{
error: error_message;
}
POST /api/attributes
Expects:
{
aKey: aKey,
name: name,
type: type
}
name
- optional (default: an empty string)
Returns on success:
Status code: 200
{
payload: attribute;
}
Returns on error:
Status codes: 400
, 409
, 500
{
error: error_message;
}
DELETE /api/attributes/:aKey
Expects:
no body.
aKey
⇒ key of the attribute (an HTTP path parameter)
Caution
If an attribute is used in a rule & if you delete this attribute, all the rules that involve this attribute will be automatically deleted.
Returns on success:
Status code: 200
{
message: "Attribute successfully deleted.";
}
Returns on error:
Status codes: 404
, 500
{
error: error_message;
}
PUT /api/attributes/:aKey
Expects:
aKey
⇒ current key of the attribute (an HTTP path parameter)
Request body:
{
aKey: aKey, // new aKey
name: name
}
name
is optional (default: an empty string)
Returns on success:
Status code: 200
{
payload: attribute;
}
Returns on error:
Status codes: 400
, 404
, 500
{
error: error_message;
}