dbdocs 0.15.1 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":"0.15.1","commands":{"build":{"id":"build","description":"build docs","strict":true,"pluginName":"dbdocs","pluginAlias":"dbdocs","pluginType":"core","aliases":[],"flags":{"project":{"name":"project","type":"option","description":"<username>/<project_name> or <project_name>","multiple":false},"public":{"name":"public","type":"boolean","description":"anyone with the URL can access","helpGroup":"sharing","allowNo":false,"exclusive":["private","password"]},"private":{"name":"private","type":"boolean","description":"only invited people can access","helpGroup":"sharing","allowNo":false,"exclusive":["public","password"]},"password":{"name":"password","type":"option","char":"p","description":"anyone with the URL + password can access","helpGroup":"sharing","multiple":false,"exclusive":["public","private"]}},"args":[{"name":"filepath","description":"dbml file path"}]},"db2dbml":{"id":"db2dbml","description":"Generate DBML directly from a database","strict":true,"pluginName":"dbdocs","pluginAlias":"dbdocs","pluginType":"core","aliases":[],"examples":"Postgres:\n $ db2dbml postgres 'postgresql://user:password@localhost:5432/dbname?schemas=schema1,schema2'\nMySQL:\n $ db2dbml mysql 'mysql://user:password@localhost:3306/dbname'\nMSSQL:\n $ db2dbml mssql 'Server=localhost,1433;Database=master;User Id=sa;Password=your_password;Encrypt=true;TrustServerCertificate=true;Schemas=schema1,schema2;'\nSnowflake:\n Password-based authentication:\n $ db2dbml snowflake 'SERVER=<account_identifier>.<region>;UID=<your_username>;PWD=<your_password>;DATABASE=<your_database>;WAREHOUSE=<your_warehouse>;ROLE=<your_role>;SCHEMAS=schema1,schema2;'\n Key pair authentication:\n $ db2dbml snowflake 'SERVER=<account_identifier>.<region>;UID=<your_username>;AUTHENTICATOR=SNOWFLAKE_JWT;PRIVATE_KEY_PATH=<path_to_your_private_key.p8>;PASSPHRASE=<your_private_key_passphrase>;DATABASE=<your_database>;WAREHOUSE=<your_warehouse>;ROLE=<your_role>;SCHEMAS=schema1,schema2;'\n \n Note: If you did not use passphrase to encrypt your private key, you can leave the \"PASSPHRASE\" empty.\n \nBigQuery:\n $ db2dbml bigquery /path_to_json_credential.json\n \n Note: Your JSON credential file must contain:\n {\n \"project_id\": \"your-project-id\",\n \"client_email\": \"your-client-email\",\n \"private_key\": \"your-private-key\",\n \"datasets\": [\"dataset_1\", \"dataset_2\", ...]\n }\n If \"datasets\" key is not provided or is empty, it will fetch all datasets.","flags":{"outFile":{"name":"outFile","type":"option","char":"o","description":"output file path","helpValue":"/path-to-your-file","multiple":false}},"args":[{"name":"database-type","description":"your database type (postgres, mysql, mssql, snowflake, bigquery)","required":true},{"name":"connection-string","description":"your database connection string (See below examples for more details)","required":true}]},"login":{"id":"login","description":"login to dbdocs\nlogin with your dbdocs credentials\n","strict":true,"pluginName":"dbdocs","pluginAlias":"dbdocs","pluginType":"core","aliases":[],"flags":{},"args":[]},"logout":{"id":"logout","description":"logout\nclears local login credentials\n","strict":true,"pluginName":"dbdocs","pluginAlias":"dbdocs","pluginType":"core","aliases":[],"flags":{},"args":[]},"ls":{"id":"ls","description":"list projects","strict":true,"pluginName":"dbdocs","pluginAlias":"dbdocs","pluginType":"core","aliases":[],"flags":{},"args":[]},"password":{"id":"password","description":"set password for your project or remove password","strict":true,"pluginName":"dbdocs","pluginAlias":"dbdocs","pluginType":"core","aliases":[],"flags":{"project":{"name":"project","type":"option","char":"p","description":"project name","helpValue":"project name","multiple":false},"set":{"name":"set","type":"option","char":"s","description":"password for your project","helpValue":"password","multiple":false},"remove":{"name":"remove","type":"boolean","char":"r","description":"remove password from your project","allowNo":false}},"args":[]},"remove":{"id":"remove","description":"remove project","strict":true,"pluginName":"dbdocs","pluginAlias":"dbdocs","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"project_name","description":"name of the project which you want to remove"}]},"rename":{"id":"rename","description":"change your username","strict":true,"pluginName":"dbdocs","pluginAlias":"dbdocs","pluginType":"core","aliases":[],"flags":{},"args":[]},"set":{"id":"set","description":"Set the Web URL and API URL of dbdocs self-hosted server","strict":true,"pluginName":"dbdocs","pluginAlias":"dbdocs","pluginType":"core","aliases":[],"examples":"$ dbdocs set --webUrl http://webserver.dev --apiUrl http://apiserver.dev","flags":{"webUrl":{"name":"webUrl","type":"option","description":"Self-hosted web url","required":true,"multiple":false},"apiUrl":{"name":"apiUrl","type":"option","description":"Self-hosted api url","required":true,"multiple":false}},"args":[]},"token":{"id":"token","description":"generate or revoke your authentication token","strict":true,"pluginName":"dbdocs","pluginAlias":"dbdocs","pluginType":"core","aliases":[],"flags":{"generate":{"name":"generate","type":"boolean","char":"g","description":"generate authentication token","allowNo":false},"revoke":{"name":"revoke","type":"boolean","char":"r","description":"revoke authentication token","allowNo":false}},"args":[]},"validate":{"id":"validate","description":"validate docs content","strict":true,"pluginName":"dbdocs","pluginAlias":"dbdocs","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"filepath","description":"dbml file path"}]}}}
1
+ {"version":"0.16.1","commands":{"build":{"id":"build","description":"build docs","strict":true,"pluginName":"dbdocs","pluginAlias":"dbdocs","pluginType":"core","aliases":[],"flags":{"project":{"name":"project","type":"option","description":"<username>/<project_name> or <project_name>","multiple":false},"public":{"name":"public","type":"boolean","description":"anyone with the URL can access","helpGroup":"sharing","allowNo":false,"exclusive":["private","password"]},"private":{"name":"private","type":"boolean","description":"only invited people can access","helpGroup":"sharing","allowNo":false,"exclusive":["public","password"]},"password":{"name":"password","type":"option","char":"p","description":"anyone with the URL + password can access","helpGroup":"sharing","multiple":false,"exclusive":["public","private"]},"versionName":{"name":"versionName","type":"option","description":"name of the version","helpValue":"your-version-name","multiple":false}},"args":[{"name":"filepath","description":"dbml file path"}]},"db2dbml":{"id":"db2dbml","description":"Generate DBML directly from a database","strict":true,"pluginName":"dbdocs","pluginAlias":"dbdocs","pluginType":"core","aliases":[],"examples":"Postgres:\n $ db2dbml postgres 'postgresql://user:password@localhost:5432/dbname?schemas=schema1,schema2'\nMySQL:\n $ db2dbml mysql 'mysql://user:password@localhost:3306/dbname'\nMSSQL:\n $ db2dbml mssql 'Server=localhost,1433;Database=master;User Id=sa;Password=your_password;Encrypt=true;TrustServerCertificate=true;Schemas=schema1,schema2;'\nSnowflake:\n Password-based authentication:\n $ db2dbml snowflake 'SERVER=<account_identifier>.<region>;UID=<your_username>;PWD=<your_password>;DATABASE=<your_database>;WAREHOUSE=<your_warehouse>;ROLE=<your_role>;SCHEMAS=schema1,schema2;'\n Key pair authentication:\n $ db2dbml snowflake 'SERVER=<account_identifier>.<region>;UID=<your_username>;AUTHENTICATOR=SNOWFLAKE_JWT;PRIVATE_KEY_PATH=<path_to_your_private_key.p8>;PASSPHRASE=<your_private_key_passphrase>;DATABASE=<your_database>;WAREHOUSE=<your_warehouse>;ROLE=<your_role>;SCHEMAS=schema1,schema2;'\n \n Note: If you did not use passphrase to encrypt your private key, you can leave the \"PASSPHRASE\" empty.\n \nBigQuery:\n $ db2dbml bigquery /path_to_json_credential.json\n \n Note: Your JSON credential file must contain:\n {\n \"project_id\": \"your-project-id\",\n \"client_email\": \"your-client-email\",\n \"private_key\": \"your-private-key\",\n \"datasets\": [\"dataset_1\", \"dataset_2\", ...]\n }\n If \"datasets\" key is not provided or is empty, it will fetch all datasets.","flags":{"outFile":{"name":"outFile","type":"option","char":"o","description":"output file path","helpValue":"/path-to-your-file","multiple":false}},"args":[{"name":"database-type","description":"your database type (postgres, mysql, mssql, snowflake, bigquery)","required":true},{"name":"connection-string","description":"your database connection string (See below examples for more details)","required":true}]},"login":{"id":"login","description":"login to dbdocs\nlogin with your dbdocs credentials\n","strict":true,"pluginName":"dbdocs","pluginAlias":"dbdocs","pluginType":"core","aliases":[],"flags":{},"args":[]},"logout":{"id":"logout","description":"logout\nclears local login credentials\n","strict":true,"pluginName":"dbdocs","pluginAlias":"dbdocs","pluginType":"core","aliases":[],"flags":{},"args":[]},"ls":{"id":"ls","description":"list projects","strict":true,"pluginName":"dbdocs","pluginAlias":"dbdocs","pluginType":"core","aliases":[],"flags":{},"args":[]},"password":{"id":"password","description":"set password for your project or remove password","strict":true,"pluginName":"dbdocs","pluginAlias":"dbdocs","pluginType":"core","aliases":[],"flags":{"project":{"name":"project","type":"option","char":"p","description":"project name","helpValue":"project name","multiple":false},"set":{"name":"set","type":"option","char":"s","description":"password for your project","helpValue":"password","multiple":false},"remove":{"name":"remove","type":"boolean","char":"r","description":"remove password from your project","allowNo":false}},"args":[]},"remove":{"id":"remove","description":"remove project","strict":true,"pluginName":"dbdocs","pluginAlias":"dbdocs","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"project_name","description":"name of the project which you want to remove"}]},"rename":{"id":"rename","description":"change your username","strict":true,"pluginName":"dbdocs","pluginAlias":"dbdocs","pluginType":"core","aliases":[],"flags":{},"args":[]},"set":{"id":"set","description":"Set the Web URL and API URL of dbdocs self-hosted server","strict":true,"pluginName":"dbdocs","pluginAlias":"dbdocs","pluginType":"core","aliases":[],"examples":"$ dbdocs set --webUrl http://webserver.dev --apiUrl http://apiserver.dev","flags":{"webUrl":{"name":"webUrl","type":"option","description":"Self-hosted web url","required":true,"multiple":false},"apiUrl":{"name":"apiUrl","type":"option","description":"Self-hosted api url","required":true,"multiple":false}},"args":[]},"token":{"id":"token","description":"generate or revoke your authentication token","strict":true,"pluginName":"dbdocs","pluginAlias":"dbdocs","pluginType":"core","aliases":[],"flags":{"generate":{"name":"generate","type":"boolean","char":"g","description":"generate authentication token","allowNo":false},"revoke":{"name":"revoke","type":"boolean","char":"r","description":"revoke authentication token","allowNo":false}},"args":[]},"validate":{"id":"validate","description":"validate docs content","strict":true,"pluginName":"dbdocs","pluginAlias":"dbdocs","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"filepath","description":"dbml file path"}]}}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dbdocs",
3
- "version": "0.15.1",
3
+ "version": "0.16.1",
4
4
  "author": "@holistics",
5
5
  "bin": {
6
6
  "dbdocs": "./bin/run"
@@ -18,6 +18,9 @@ const { getProjectUrl, parseProjectName } = require('../utils/helper');
18
18
  const { getIsPublicValueFromBuildFlag } = require('../utils/helper');
19
19
  const { formatParserV2ErrorMessage } = require('../utils/error-formatter');
20
20
 
21
+ // Max length is 255 (STRING type in Postgres)
22
+ const VERSION_NAME_MAX_LENGTH = 255;
23
+
21
24
  async function build (project, authConfig) {
22
25
  const res = await axios.post(`${vars.apiUrl}/projects`, project, authConfig);
23
26
  if (![200, 201].includes(res.status)) {
@@ -51,10 +54,10 @@ class BuildCommand extends Command {
51
54
  const spinner = ora({});
52
55
 
53
56
  try {
54
- const authConfig = await verifyToken();
57
+ const { authConfig } = await verifyToken();
55
58
  const { flags, args } = await this.parse(BuildCommand);
56
59
  const {
57
- public: publicFlag, private: privateFlag, password, project,
60
+ public: publicFlag, private: privateFlag, password, project, versionName,
58
61
  } = flags;
59
62
 
60
63
  const { filepath } = args;
@@ -74,6 +77,8 @@ class BuildCommand extends Command {
74
77
  else [orgName, projectNameFromInput] = parseRes;
75
78
  }
76
79
 
80
+ if (versionName && versionName.length > VERSION_NAME_MAX_LENGTH) throw new Error('The version name is too long, please shorten it.');
81
+
77
82
  // validate dbml syntax, get project name if it's already defined in the file
78
83
  spinner.text = 'Parsing file content';
79
84
  spinner.start();
@@ -126,6 +131,7 @@ class BuildCommand extends Command {
126
131
  orgName,
127
132
  doc: {
128
133
  content,
134
+ name: versionName,
129
135
  },
130
136
  shallowSchema: model.schemas,
131
137
  normalizedDatabase: model.normalizedDatabase,
@@ -205,6 +211,10 @@ BuildCommand.flags = {
205
211
  password: Flags.string({
206
212
  char: 'p', description: 'anyone with the URL + password can access', helpGroup: FLAG_HELP_GROUP.sharing, exclusive: ['public', 'private'],
207
213
  }),
214
+ versionName: Flags.string({
215
+ description: 'name of the version',
216
+ helpValue: 'your-version-name',
217
+ }),
208
218
  };
209
219
 
210
220
  BuildCommand.args = [
@@ -2,11 +2,17 @@ const { Command, CliUx } = require('@oclif/core');
2
2
  const chalk = require('chalk');
3
3
  const { vars } = require('../vars');
4
4
  const verifyToken = require('../utils/verifyToken');
5
- const { getProjectsByOrg, getProjectUserRolesByOrg } = require('../utils/org');
6
- const { getOrg } = require('../utils/org');
5
+ const { getProjectsByOrg } = require('../utils/org');
6
+ const { getAssociatedOrgs } = require('../utils/org');
7
7
  const { PROJECT_SHARING_TEXT } = require('../utils/constants');
8
8
  const { getProjectUrl } = require('../utils/helper');
9
9
 
10
+ const ROLE = {
11
+ viewer: 'viewer',
12
+ editor: 'editor',
13
+ admin: 'admin',
14
+ };
15
+
10
16
  const ROLE_DESCRIPTIONS = {
11
17
  viewer: 'View only',
12
18
  editor: 'View and edit',
@@ -14,77 +20,92 @@ const ROLE_DESCRIPTIONS = {
14
20
  noAccess: 'No access',
15
21
  };
16
22
 
23
+ /**
24
+ * The `LsCommand` class is responsible for listing all projects associated with the user.
25
+ * It retrieves projects from various orgs (owned, member, and guest) and displays them in a formatted table,
26
+ * including details about project ownership, user permissions, and general access control.
27
+ */
17
28
  class LsCommand extends Command {
18
- showProjects (projects, orgName) {
19
- const [maxUrlWidth, maxUpdatedAtWidth] = projects.reduce((accumulator, project) => {
20
- const url = getProjectUrl(vars.hostUrl, orgName, project.urlName);
29
+ /**
30
+ * Displays a formatted table of projects for a given org.
31
+ * It calculates column widths dynamically and presents project details, ownership, permissions, and general access control.
32
+ *
33
+ * @param {Object} org - The org object associated with the projects.
34
+ * @param {Object} user - The current user object.
35
+ * @param {boolean} isOrgOwner - Indicates if the current user is the owner of the org.
36
+ */
37
+ showProjects (org, user, isOrgOwner) {
38
+ const projects = this.flattenAllOrgProjects(org);
39
+
40
+ // The starting width for each column (equals to the column names)
41
+ // These widths are then increased to contains the values in each column
42
+ // The columns are
43
+ // 1. `url`
44
+ // 2. `updated at`
45
+ // 3. `owner`
46
+ const defaultColumnsWidth = [3, 12, 5];
47
+
48
+ const [maxUrlWidth, maxUpdatedAtWidth, maxOwnerWidth] = projects.reduce((accumulator, project) => {
49
+ const url = getProjectUrl(vars.hostUrl, org.name, project.urlName);
21
50
  const updatedAt = (new Date(project.updatedAt)).toLocaleString();
51
+ const owner = this.getProjectOwnerStr(project, user);
22
52
  return [
23
- accumulator[0] > url.length ? accumulator[0] : url.length,
24
- accumulator[1] > updatedAt.length ? accumulator[1] : updatedAt.length,
53
+ Math.max(accumulator[0], url.length),
54
+ Math.max(accumulator[1], updatedAt.length),
55
+ Math.max(accumulator[2], owner.length),
25
56
  ];
26
- }, [3, 12]);
57
+ }, defaultColumnsWidth);
58
+
59
+ this.log(chalk.bold(org.name));
27
60
 
28
- this.log(chalk.bold(orgName));
61
+ const projectNameMinWidth = 20;
62
+ const projectPermissionMinWidth = 15;
63
+ const projectGeneralAccessMinWidth = 20;
29
64
 
30
65
  CliUx.ux.table(projects, {
31
66
  name: {
32
- minWidth: 20,
67
+ minWidth: projectNameMinWidth,
33
68
  },
34
- generalAccess: {
35
- header: 'Access Control',
36
- minWidth: 20,
37
- get: (project) => PROJECT_SHARING_TEXT[project.generalAccessType],
69
+ owner: {
70
+ minWidth: maxOwnerWidth + 2,
71
+ get: (project) => this.getProjectOwnerStr(project, user),
38
72
  },
39
- url: {
40
- minWidth: maxUrlWidth + 2,
41
- get: (project) => chalk.cyan(getProjectUrl(vars.hostUrl, orgName, project.urlName)),
42
- },
43
- updatedAt: {
44
- minWidth: maxUpdatedAtWidth + 2,
45
- header: 'Last updated',
46
- get: (project) => (new Date(project.updatedAt)).toLocaleString(),
47
- },
48
- }, {
49
- printLine: this.log.bind(this),
50
- });
51
-
52
- if (!projects.length) CliUx.ux.log('', '(empty)');
53
- }
73
+ permission: {
74
+ minWidth: projectPermissionMinWidth,
75
+ // The diagram to determine the user permission in a project is in the link below
76
+ // https://www.notion.so/holistics/Roles-Permission-control-127f89dc7e4980fea7efc0526a3d93f2?source=copy_link#13ff89dc7e498033a568f4de6fa4c280
77
+ get: (project) => {
78
+ if (this.isProjectOwner(project, user)) return ROLE_DESCRIPTIONS.admin;
54
79
 
55
- showSharedProjects (sharedProjectsData) {
56
- const [maxOrgWidth, maxUrlWidth, maxUpdatedAtWidth, projects] = sharedProjectsData.reduce((accumulator, sharedData) => {
57
- const { Project: project, Role: role } = sharedData;
80
+ const isInvitedAsEditor = project.role?.name === ROLE.editor;
58
81
 
59
- const url = getProjectUrl(vars.hostUrl, project.Org.name, project.urlName);
60
- const updatedAt = (new Date(project.updatedAt)).toLocaleString();
82
+ // If the user is a guest (invited to the project, but not invited to the org)
83
+ // then the final permission is the invited permission
84
+ const isGuest = !org.orgRoleName && !isOrgOwner;
85
+ if (isGuest) return isInvitedAsEditor ? ROLE_DESCRIPTIONS.editor : ROLE_DESCRIPTIONS.viewer;
61
86
 
62
- accumulator[3].push({ ...project, org: project.Org.name, role });
63
- return [
64
- accumulator[0] > project.Org.name.length ? accumulator[0] : project.Org.name.length,
65
- accumulator[1] > url.length ? accumulator[1] : url.length,
66
- accumulator[2] > updatedAt.length ? accumulator[2] : updatedAt.length,
67
- accumulator[3],
68
- ];
69
- }, [3, 3, 12, []]);
87
+ // Org viewers never have permission to edit project
88
+ const isOrgEditor = isOrgOwner || (org.orgRoleName === ROLE.editor);
89
+ if (!isOrgEditor) return ROLE_DESCRIPTIONS.viewer;
70
90
 
71
- this.log(chalk.bold('Shared with me'));
91
+ const isProjectSharedForEditInOrg = project.OrgRole?.name === ROLE.editor;
72
92
 
73
- CliUx.ux.table(projects, {
74
- name: {
75
- minWidth: 20,
93
+ return (isProjectSharedForEditInOrg || isInvitedAsEditor) ? ROLE_DESCRIPTIONS.editor : ROLE_DESCRIPTIONS.viewer;
94
+ },
76
95
  },
77
- org: {
78
- header: 'Owner',
79
- minWidth: maxOrgWidth + 2,
80
- },
81
- permission: {
82
- minWidth: 15,
83
- get: (project) => ROLE_DESCRIPTIONS[project.role.name],
96
+ generalAccess: {
97
+ header: 'Access Control',
98
+ minWidth: projectGeneralAccessMinWidth,
99
+ get: (project) => {
100
+ const canViewGeneralAccess = this.isProjectOwner(project, user) && project.generalAccessType;
101
+ return canViewGeneralAccess
102
+ ? PROJECT_SHARING_TEXT[project.generalAccessType]
103
+ : 'N/A';
104
+ },
84
105
  },
85
106
  url: {
86
107
  minWidth: maxUrlWidth + 2,
87
- get: (project) => chalk.cyan(getProjectUrl(vars.hostUrl, project.org, project.urlName)),
108
+ get: (project) => chalk.cyan(getProjectUrl(vars.hostUrl, org.name, project.urlName)),
88
109
  },
89
110
  updatedAt: {
90
111
  minWidth: maxUpdatedAtWidth + 2,
@@ -96,20 +117,61 @@ class LsCommand extends Command {
96
117
  });
97
118
 
98
119
  if (!projects.length) CliUx.ux.log('', '(empty)');
120
+ this.log();
99
121
  }
100
122
 
123
+ // eslint-disable-next-line class-methods-use-this
124
+ isProjectOwner (project, user) {
125
+ return user.id && project.Owner?.id === user.id;
126
+ }
127
+
128
+ getProjectOwnerStr (project, user) {
129
+ return this.isProjectOwner(project, user)
130
+ ? `${user.email} (you)`
131
+ : project.Owner?.email || '';
132
+ }
133
+
134
+ /**
135
+ * Consolidates all types of projects (owned, shared, and private) associated with a given org into a single list.
136
+ *
137
+ * @param {Object} org - The org object, potentially containing `projects`, `sharedProjects`, and `privateProjects` arrays.
138
+ * @returns {Array<Object>} An array containing all projects (owned, shared, and private) from the org.
139
+ */
140
+ // eslint-disable-next-line class-methods-use-this
141
+ flattenAllOrgProjects (org) {
142
+ const { projects = [], sharedProjects = [], privateProjects = [] } = org;
143
+ return [...projects, ...sharedProjects, ...privateProjects];
144
+ }
145
+
146
+ /**
147
+ * Main entry point for the `ls` command.
148
+ * It retrieves and displays all projects associated with the user across various orgs (owned, member, and guest).
149
+ *
150
+ * @returns {Promise<void>}
151
+ */
101
152
  async run () {
102
153
  try {
103
- const authConfig = await verifyToken();
104
- const org = await getOrg(authConfig);
105
- const [projects, sharedProjectsData] = await Promise.all([
106
- getProjectsByOrg(org.name, authConfig),
107
- getProjectUserRolesByOrg(org.name, authConfig),
108
- ]);
109
-
110
- this.showProjects(projects, org.name);
111
- this.log('\n');
112
- this.showSharedProjects(sharedProjectsData);
154
+ const { authConfig, account } = await verifyToken();
155
+ const { ownedOrgs, memberOrgs, guestOrgs } = await getAssociatedOrgs(authConfig);
156
+
157
+ const getOwnedOrgsProjectsPromise = Promise.all(ownedOrgs.map((org) => getProjectsByOrg(org.name, authConfig)));
158
+
159
+ const getJoinedOrgsPromise = Promise.all(memberOrgs.map(async ({ Org, Role }) => {
160
+ const orgWithProjects = await getProjectsByOrg(Org.name, authConfig);
161
+ return { ...orgWithProjects, orgRoleName: Role.name };
162
+ }));
163
+
164
+ const getGuestOrgsProjectsPromise = Promise.all(guestOrgs.map((org) => getProjectsByOrg(org.name, authConfig)));
165
+
166
+ const [
167
+ ownedOrgsProjects,
168
+ joinedOrgs,
169
+ guestOrgsProjects,
170
+ ] = await Promise.all([getOwnedOrgsProjectsPromise, getJoinedOrgsPromise, getGuestOrgsProjectsPromise]);
171
+
172
+ ownedOrgsProjects.forEach((org) => this.showProjects(org, account, true));
173
+ joinedOrgs.forEach((org) => this.showProjects(org, account, false));
174
+ guestOrgsProjects.forEach((org) => this.showProjects(org, account, false));
113
175
  } catch (err) {
114
176
  this.error(err);
115
177
  }
@@ -29,7 +29,7 @@ class PasswordCommand extends Command {
29
29
  if (set && remove) {
30
30
  throw new Error('You must choose one, set password or remove.');
31
31
  }
32
- const authConfig = await verifyToken();
32
+ const { authConfig } = await verifyToken();
33
33
  const org = await getOrg(authConfig);
34
34
 
35
35
  if (!project) {
@@ -19,7 +19,7 @@ class RemoveCommand extends Command {
19
19
  async run () {
20
20
  const spinner = ora({});
21
21
  try {
22
- const authConfig = await verifyToken();
22
+ const { authConfig } = await verifyToken();
23
23
  const org = await getOrg(authConfig);
24
24
 
25
25
  const { args } = await this.parse(RemoveCommand);
@@ -43,7 +43,7 @@ class RenameCommand extends Command {
43
43
  async run () {
44
44
  const spinner = ora({});
45
45
  try {
46
- const authConfig = await verifyToken();
46
+ const { authConfig } = await verifyToken();
47
47
 
48
48
  this.warn('After renaming, your authentication token (if exists) will be revoked. Please re-generate a new one!');
49
49
  this.warn('You may need to re-login your account on the dbdocs web app for the best user experience.');
@@ -15,7 +15,7 @@ class TokenCommand extends Command {
15
15
  this.error('Please specify only one action.');
16
16
  }
17
17
  spinner.start('Verify your identity.');
18
- const authConfig = await verifyToken().catch((error) => {
18
+ const { authConfig } = await verifyToken().catch((error) => {
19
19
  spinner.fail();
20
20
  return Promise.reject(error);
21
21
  });
package/src/utils/org.js CHANGED
@@ -5,17 +5,19 @@ async function getOrg (authConfig) {
5
5
  const { data: { orgs } } = await axios.get(`${vars.apiUrl}/orgs`, authConfig);
6
6
  return orgs[0];
7
7
  }
8
- async function getProjectsByOrg (orgName, authConfig) {
9
- const { data: { org: { projects } } } = await axios.get(`${vars.apiUrl}/orgs/${orgName}/projects`, authConfig);
10
- return projects;
8
+
9
+ async function getAssociatedOrgs (authConfig) {
10
+ const { data: { ownedOrgs, memberOrgs, guestOrgs } } = await axios.get(`${vars.apiUrl}/org_user_roles`, authConfig);
11
+ return { ownedOrgs, memberOrgs, guestOrgs };
11
12
  }
12
- async function getProjectUserRolesByOrg (orgName, authConfig) {
13
- const { data } = await axios.get(`${vars.apiUrl}/orgs/${orgName}/project_user_roles`, authConfig);
14
- return data;
13
+
14
+ async function getProjectsByOrg (orgName, authConfig) {
15
+ const { data: { org } } = await axios.get(`${vars.apiUrl}/orgs/${orgName}/projects`, authConfig);
16
+ return org;
15
17
  }
16
18
 
17
19
  module.exports = {
18
20
  getOrg,
21
+ getAssociatedOrgs,
19
22
  getProjectsByOrg,
20
- getProjectUserRolesByOrg,
21
23
  };
@@ -2,6 +2,13 @@ const netrc = require('netrc-parser').default;
2
2
  const axios = require('axios');
3
3
  const { vars } = require('../vars');
4
4
 
5
+ /**
6
+ * Verifies the user's authentication token, either from .netrc or DBDOCS_TOKEN environment variable.
7
+ * It ensures the user is logged in and the token is valid by making an API call to the account endpoint.
8
+ *
9
+ * @returns {Promise<{authConfig: object, account: object}>} An object containing the authentication configuration and the user's account details.
10
+ * @throws {Error} If the user is not authenticated, or if the token is invalid.
11
+ */
5
12
  const verifyToken = async () => {
6
13
  const { apiHost } = vars;
7
14
  await netrc.load();
@@ -12,20 +19,20 @@ const verifyToken = async () => {
12
19
  throw new Error('Please login first.');
13
20
  }
14
21
  const authToken = dbdocsToken || netrc.machines[apiHost].password;
15
- const configuration = {
22
+ const authConfig = {
16
23
  headers: {
17
24
  Authorization: authToken,
18
25
  'Authorization-Method': dbdocsToken ? 'dbdocs-token' : 'login',
19
26
  },
20
27
  };
21
- await axios.get(`${vars.apiUrl}/account`, configuration)
28
+ const { data: { account } } = await axios.get(`${vars.apiUrl}/account`, authConfig)
22
29
  .catch((error) => {
23
30
  if (error.response && error.response.data.error.name === 'InvalidDbdocsToken') {
24
31
  throw new Error('Your DBDOCS_TOKEN is invalid, please setup a new one or remove it then login again.');
25
32
  }
26
33
  throw new Error('Invalid token. Please login again and/or check your DBDOCS_TOKEN.');
27
34
  });
28
- return configuration;
35
+ return { authConfig, account };
29
36
  };
30
37
 
31
38
  module.exports = verifyToken;