Mentions légales du service

Skip to content
Snippets Groups Projects
Commit 276cc09c authored by Yannick Li's avatar Yannick Li
Browse files

Merge branch '25-update-api-provided-by-c-service' into 'dev'

Resolve "Update API provided by c-service"

See merge request !28
parents e3a383ca 380a780f
No related branches found
No related tags found
2 merge requests!48Release v1.1.4,!28Resolve "Update API provided by c-service"
Pipeline #193285 passed
...@@ -35,10 +35,16 @@ npm install ...@@ -35,10 +35,16 @@ npm install
2.**Start C-Service** 2.**Start C-Service**
First, one need to compile build the project:
```bash
npm run build
```
Set database name and credentials and run service: Set database name and credentials and run service:
```bash ```bash
export DBNAME=my-database COUCHDB_USER=my-user COUCHDB_PASSWORD=my-passwd export COUCHDB_USER=my-user COUCHDB_PASSWORD=my-passwd
npm start npm start
``` ```
...@@ -48,6 +54,7 @@ You might need to change the binding address in the CouchDB admin panel to allow ...@@ -48,6 +54,7 @@ You might need to change the binding address in the CouchDB admin panel to allow
You can also run tests using: You can also run tests using:
```bash ```bash
export DBNAME=my-dbname COUCHDB_USER=my-user COUCHDB_PASSWORD=my-passwd
npm test npm test
``` ```
......
This diff is collapsed.
...@@ -26,22 +26,10 @@ import * as bodyParser from "body-parser"; ...@@ -26,22 +26,10 @@ import * as bodyParser from "body-parser";
import express from "express"; import express from "express";
import { makeExecutableSchema } from "graphql-tools"; import { makeExecutableSchema } from "graphql-tools";
import Nano, { MaybeDocument } from "nano"; import Nano, { MaybeDocument } from "nano";
import PouchDBImpl from "pouchdb";
import { OpenAPI, useSofa } from "sofa-api"; import { OpenAPI, useSofa } from "sofa-api";
import { Document } from "./Database/DataTypes/Interfaces/Types";
import PouchDBDataSource, {
AdapterParams,
ConnectionProtocol,
} from "./Database/Implementation/PouchDB/DataSource/PouchDBDataSource";
import { Connection, DatabaseHooks } from "./Database/Interfaces/Types";
import CRDTWrapper from "./Utils/CRDTWrapper";
const maxRetryTimeout = 30000;
// http://USERNAME:PASSWORD/URL:PORT // http://USERNAME:PASSWORD/URL:PORT
const dbUrl = process.env.COUCHDB_URL || "http://localhost:5984/"; const dbUrl = process.env.COUCHDB_URL || "http://localhost:5984/";
const dbName = process.env.DBNAME || "";
const serviceName = process.env.SERVICE_NAME || "couchdb";
const clientId = "STATIC_CLIENT_ID";
const username = process.env.COUCHDB_USER || undefined; const username = process.env.COUCHDB_USER || undefined;
const userpass = process.env.COUCHDB_PASSWORD || undefined; const userpass = process.env.COUCHDB_PASSWORD || undefined;
...@@ -62,21 +50,18 @@ const typeDefs = gql` ...@@ -62,21 +50,18 @@ const typeDefs = gql`
state: String state: String
} }
type AppObject {
id: String!
document: String
}
type Query { type Query {
getObjects(appName: String): [String]
getObject(appName: String, id: ID!): String
replicators: [Replicator] replicators: [Replicator]
replicator(id: ID): Replicator replicator(id: ID!): Replicator
appObjects: [AppObject]
} }
type Mutation { type Mutation {
replicator(source: String, target: String, continuous: Boolean): String
createApp(appName: String): String createApp(appName: String): String
deleteApp(appName: String): String deleteApp(appName: String): String
updateObject(appName: String, id: ID!, document: String): String
replicator(source: String, target: String, continuous: Boolean): String
} }
schema { schema {
...@@ -85,19 +70,16 @@ const typeDefs = gql` ...@@ -85,19 +70,16 @@ const typeDefs = gql`
} }
`; `;
let connection: Connection;
const resolvers = { const resolvers = {
Mutation: { Mutation: {
createApp: (_: any, { appName }: any) => { createApp(_: any, { appName }: any) {
return client.db.create(appName).then((body) => { return createApp(appName);
return "ok";
});
}, },
deleteApp: (_: any, { appName }: any) => { deleteApp(_: any, { appName }: any) {
client.db.destroy(appName).then((body) => { return deleteApp(appName);
return "ok"; },
}); updateObject: (_: any, { appName, id, document }: any) => {
return updateObject(appName, id, document);
}, },
replicator: (_: any, { source, target, continuous }: any) => { replicator: (_: any, { source, target, continuous }: any) => {
return client.db return client.db
...@@ -105,30 +87,17 @@ const resolvers = { ...@@ -105,30 +87,17 @@ const resolvers = {
continuous, continuous,
create_target: true, create_target: true,
}) })
.then((body: any) => "ok"); .then((body) => "ok")
.catch((error) => console.error("error", error));
}, },
}, },
Query: { Query: {
appObjects: () => getObjects: (_: any, { appName }: any) => {
appDB.list().then((objs) => return getObjects(appName);
objs.rows.map((r) => { },
return connection getObject: (_: any, { appName, id }: any) => {
.get<CRDTWrapper<any>>(r.id) return getObject(appName, id);
.then((obj: Document<CRDTWrapper<any>>) => { },
const { unwrapped } = CRDTWrapper.unwrap(obj.current());
return { id: r.id, document: JSON.stringify(unwrapped.value()) };
})
.catch((error) => console.error("error", error));
})
),
replicator: async (_: any, { id }: any) =>
replicator.get(id).then((r: any) => ({
continuous: r.continuous,
id: r._id,
source: r.source.url,
state: r._replication_state,
target: r.target.url,
})),
replicators: async (): Promise<IReplicator[]> => { replicators: async (): Promise<IReplicator[]> => {
const list = await replicator.list(); const list = await replicator.list();
return Promise.all(list.rows.map((r) => replicator.get(r.id))).then( return Promise.all(list.rows.map((r) => replicator.get(r.id))).then(
...@@ -144,6 +113,14 @@ const resolvers = { ...@@ -144,6 +113,14 @@ const resolvers = {
})) }))
); );
}, },
replicator: async (_: any, { id }: any) =>
replicator.get(id).then((r: any) => ({
continuous: r.continuous,
id: r._id,
source: r.source.url,
state: r._replication_state,
target: r.target.url,
})),
}, },
}; };
...@@ -175,8 +152,6 @@ app.use( ...@@ -175,8 +152,6 @@ app.use(
}) })
); );
// app.use(bodyParser.json());
const server = new ApolloServer({ const server = new ApolloServer({
resolvers, resolvers,
typeDefs, typeDefs,
...@@ -187,89 +162,163 @@ server.applyMiddleware({ app }); ...@@ -187,89 +162,163 @@ server.applyMiddleware({ app });
app.listen({ port: 4000 }); app.listen({ port: 4000 });
/** /**
* DBNAME is required env arg for the application, exit if unset * Creates a new database if it does not already exists
*
* TODO: init databse with defaults
*
* @param dbName the new database name
*/
function createApp(dbName: string) {
console.warn(`[SERVER] Creating database: '${dbName}'`);
client.db
.use(dbName)
.info()
.catch((error) => {
client.db.create(dbName).catch((error) => {
console.error(`[SERVER][ERROR] Failed creating database '${dbName}'`);
console.error(error);
});
});
return "OK";
}
/**
* Deletes a database if it exists
*
* @param dbName the targeted database name
*/ */
if (!dbName) { function deleteApp(dbName: string) {
console.error("Please set DBNAME environment variable"); console.warn(`[SERVER] Deleting database: '${dbName}'`);
process.exit(1); client.db
.use(dbName)
.info()
.then((body) => {
client.db.destroy(dbName).catch((error) => {
console.error(`[SERVER][ERROR] Failed deleting database '${dbName}'`);
console.error(error);
});
})
.catch((error) => {
return;
});
return "OK";
} }
/** /**
* Creates a new Database, used when DBNAME doesn't exist * Updates the content of an object inside a database
* *
* In case of failure, the server will exist with error * TODO handle concurrent updates
* TODO: init DB with defaults
* *
* @param appDB CouchDB DatabaseScope instance where to create the new DB * @param dbName the targeted database name
* @param dbName new DB name * @param docName the targeted document name
* @param document the document new content
*/ */
function createDB(appDB: Nano.DatabaseScope, dbName: string) { function updateObject(dbName: string, docName: string, document: string) {
console.warn("[SERVER] Creating DB:" + dbName); console.warn(
appDB.create(dbName).catch((error) => { `[SERVER] Updating document '${docName}' in database '${dbName}'`
console.error(`[SERVER][ERROR] Failed creating database ${dbName}`); );
console.error(error); client.db
process.exit(1); .use(dbName)
}); .info()
.then((body) => {
client.db
.use(dbName)
.get(docName)
.then((body) => {
const newDocument = JSON.parse(document);
newDocument._rev = body._rev;
client.db
.use(dbName)
.insert(newDocument, docName)
.catch((error) => {
console.error(
`[SERVER][ERROR] Failed updating document '${docName}' in database '${dbName}'`
);
console.error(error);
});
})
.catch((error) => {
client.db
.use(dbName)
.insert(JSON.parse(document), docName)
.catch((error) => {
console.error(
`[SERVER][ERROR] Failed updating document '${docName}' in database '${dbName}'`
);
console.error(error);
});
});
})
.catch((error) => {
console.error(
`[SERVER][ERROR] Failed updating document '${docName}' in database '${dbName}'`
);
console.error(error);
});
return "OK";
} }
/** /**
* Poll appDB waiting for connection ack * Gets all documents from a given database.
* *
* If DBNAME doesn't exist, it will be created * @param dbName the targeted database name
*/
function getObjects(dbName: string) {
return client.db
.use(dbName)
.list()
.then((objs) =>
objs.rows.map((r) => {
return client.db
.use(dbName)
.get(r.id)
.then((body) => {
return JSON.stringify(body);
})
.catch((error) => {
console.error(
`[SERVER][ERROR] Failed getting objects in database '${dbName}'`
);
console.error("error", error);
});
})
)
.catch((error) => {
console.error(
`[SERVER][ERROR] Failed getting objects in database '${dbName}'`
);
console.error("error", error);
});
}
/**
* Gets a given document from a given database.
* *
* @param timeout polling sleep duration, multiplied by 2 at each retry * @param dbName the targeted database name
* @param docName the targeted document name
*/ */
const connectDB = (timeout = 1000) => function getObject(dbName: string, docName: string) {
appDB.info().catch((error) => { return client.db
const retryIn = Math.min(timeout * 2, maxRetryTimeout); .use(dbName)
setTimeout(() => connectDB(retryIn), retryIn); .get(docName)
console.warn(error, `retry in ${retryIn}`); .then((body) => {
// If DB doesn't exist, create it before first retry return JSON.stringify(body);
if (error && error.error === "not_found" && timeout <= 1000) { })
createDB(client.db, dbName); .catch((error) => {
} console.error(
}); `[SERVER][ERROR] Failed getting object '${docName}' in database '${dbName}'`
);
console.error("error", error);
});
}
const client = Nano({ url: dbUrl, requestDefaults: { jar: true } }); const client = Nano({ url: dbUrl, requestDefaults: { jar: true } });
if (username && userpass) { if (username && userpass) {
client.auth(username, userpass).catch((err) => console.error(err)); client.auth(username, userpass).catch((error) => {
console.error("[SERVER][ERROR] Authentification failed");
console.error(error);
});
} }
const appDB = client.db.use(dbName);
const replicator = client.db.use("_replicator"); const replicator = client.db.use("_replicator");
// dumpNetworkServices();
connectDB().catch((error: any) => console.error(error));
const hooks: DatabaseHooks = {
conflictHandler: (
obj: Document<CRDTWrapper<any>>,
objs: Array<Document<CRDTWrapper<any>>>
) => {
const crdt = CRDTWrapper.unwrap(obj.current());
if (objs.length > 0) {
objs.forEach((o) => {
const otherCrdt = CRDTWrapper.unwrap(o.current());
crdt.merge(otherCrdt);
});
return CRDTWrapper.wrap(crdt);
}
throw new Error("Unexpected call");
},
};
// TODO: support database url parameter. This might not work properly
const database: AdapterParams = {
connectionParams: {},
dbName,
url: dbUrl,
};
const dataSource = new PouchDBDataSource(PouchDBImpl, database);
dataSource
.connection({ autoSave: false, handleConflicts: true })
.then((newConnection) => {
connection = newConnection;
connection.registerHooks(hooks);
});
...@@ -8,16 +8,18 @@ components: ...@@ -8,16 +8,18 @@ components:
Query: Query:
type: object type: object
properties: properties:
getObjects:
type: array
items:
type: string
getObject:
type: string
replicators: replicators:
type: array type: array
items: items:
$ref: "#/components/schemas/Replicator" $ref: "#/components/schemas/Replicator"
replicator: replicator:
$ref: "#/components/schemas/Replicator" $ref: "#/components/schemas/Replicator"
appObjects:
type: array
items:
$ref: "#/components/schemas/AppObject"
Replicator: Replicator:
type: object type: object
required: required:
...@@ -33,21 +35,14 @@ components: ...@@ -33,21 +35,14 @@ components:
type: boolean type: boolean
state: state:
type: string type: string
AppObject:
type: object
required:
- id
properties:
id:
type: string
document:
type: string
Mutation: Mutation:
type: object type: object
properties: properties:
replicator:
type: string
createApp: createApp:
type: string type: string
deleteApp: deleteApp:
type: string type: string
updateObject:
type: string
replicator:
type: string
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment