Explorar o código

Merge branch 'master' into feat/admin-disable-link-sharing

hakumizuki %!s(int64=4) %!d(string=hai) anos
pai
achega
03c4fea6c2

+ 6 - 0
packages/slack/src/interfaces/request-between-growi-and-proxy.ts

@@ -6,6 +6,12 @@ export type RequestFromGrowi = Request & {
 
 
   // will be extracted from header
   // will be extracted from header
   tokenGtoPs: string[],
   tokenGtoPs: string[],
+
+  // Block Kit properties
+  body: {
+    view?: string,
+    blocks?: string
+  },
 };
 };
 
 
 export type RequestFromProxy = Request & {
 export type RequestFromProxy = Request & {

+ 31 - 31
packages/slackbot-proxy/src/controllers/growi-to-slack.ts

@@ -3,7 +3,7 @@ import {
 } from '@tsed/common';
 } from '@tsed/common';
 import axios from 'axios';
 import axios from 'axios';
 
 
-import { WebAPICallOptions, WebAPICallResult } from '@slack/web-api';
+import { WebAPICallResult } from '@slack/web-api';
 
 
 import {
 import {
   verifyGrowiToSlackRequest, getConnectionStatuses, getConnectionStatus, generateWebClient,
   verifyGrowiToSlackRequest, getConnectionStatuses, getConnectionStatus, generateWebClient,
@@ -18,8 +18,8 @@ import { OrderRepository } from '~/repositories/order';
 
 
 import { InstallerService } from '~/services/InstallerService';
 import { InstallerService } from '~/services/InstallerService';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
-import { findInjectorByType } from '~/services/growi-uri-injector/GrowiUriInjectorFactory';
-import { injectGrowiUriToView } from '~/utils/injectGrowiUriToView';
+import { ViewInteractionPayloadDelegator } from '~/services/growi-uri-injector/ViewInteractionPayloadDelegator';
+import { ActionsBlockPayloadDelegator } from '~/services/growi-uri-injector/ActionsBlockPayloadDelegator';
 
 
 
 
 const logger = loggerFactory('slackbot-proxy:controllers:growi-to-slack');
 const logger = loggerFactory('slackbot-proxy:controllers:growi-to-slack');
@@ -42,6 +42,12 @@ export class GrowiToSlackCtrl {
   @Inject()
   @Inject()
   orderRepository: OrderRepository;
   orderRepository: OrderRepository;
 
 
+  @Inject()
+  viewInteractionPayloadDelegator: ViewInteractionPayloadDelegator;
+
+  @Inject()
+  actionsBlockPayloadDelegator: ActionsBlockPayloadDelegator;
+
   async requestToGrowi(growiUrl:string, tokenPtoG:string):Promise<void> {
   async requestToGrowi(growiUrl:string, tokenPtoG:string):Promise<void> {
     const url = new URL('/_api/v3/slack-integration/proxied/commands', growiUrl);
     const url = new URL('/_api/v3/slack-integration/proxied/commands', growiUrl);
     await axios.post(url.toString(), {
     await axios.post(url.toString(), {
@@ -171,41 +177,32 @@ export class GrowiToSlackCtrl {
     return res.send({ relation: createdRelation, slackBotToken: token });
     return res.send({ relation: createdRelation, slackBotToken: token });
   }
   }
 
 
-  injectGrowiUri(req:GrowiReq, growiUri:string):WebAPICallOptions {
+  injectGrowiUri(req: GrowiReq, growiUri: string): void {
+    if (req.body.view == null && req.body.blocks == null) {
+      return;
+    }
 
 
     if (req.body.view != null) {
     if (req.body.view != null) {
-      injectGrowiUriToView(req.body, growiUri);
+      const parsedElement = JSON.parse(req.body.view);
+      // delegate to ViewInteractionPayloadDelegator
+      if (this.viewInteractionPayloadDelegator.shouldHandleToInject(parsedElement)) {
+        this.viewInteractionPayloadDelegator.inject(parsedElement, growiUri);
+        req.body.view = JSON.stringify(parsedElement);
+      }
     }
     }
-
-    if (req.body.blocks != null) {
-      const parsedBlocks = JSON.parse(req.body.blocks as string);
-
-      parsedBlocks.forEach((parsedBlock) => {
-        if (parsedBlock.type !== 'actions') {
-          return;
-        }
-        parsedBlock.elements.forEach((element) => {
-          const growiUriInjector = findInjectorByType(element.type);
-          if (growiUriInjector != null) {
-            growiUriInjector.inject(element, growiUri);
-          }
-        });
-
-        return;
-      });
-
-      req.body.blocks = JSON.stringify(parsedBlocks);
+    else if (req.body.blocks != null) {
+      const parsedElement = JSON.parse(req.body.blocks);
+      // delegate to ActionsBlockPayloadDelegator
+      if (this.actionsBlockPayloadDelegator.shouldHandleToInject(parsedElement)) {
+        this.actionsBlockPayloadDelegator.inject(parsedElement, growiUri);
+        req.body.blocks = JSON.stringify(parsedElement);
+      }
     }
     }
-
-    const opt = req.body;
-    opt.headers = req.headers;
-
-    return opt;
   }
   }
 
 
   @Post('/:method')
   @Post('/:method')
   @UseBefore(AddWebclientResponseToRes, verifyGrowiToSlackRequest)
   @UseBefore(AddWebclientResponseToRes, verifyGrowiToSlackRequest)
-  async postResult(
+  async callSlackApi(
     @PathParams('method') method: string, @Req() req: GrowiReq, @Res() res: WebclientRes,
     @PathParams('method') method: string, @Req() req: GrowiReq, @Res() res: WebclientRes,
   ): Promise<void|string|Res|WebAPICallResult> {
   ): Promise<void|string|Res|WebAPICallResult> {
     const { tokenGtoPs } = req;
     const { tokenGtoPs } = req;
@@ -234,7 +231,10 @@ export class GrowiToSlackCtrl {
     const client = generateWebClient(token);
     const client = generateWebClient(token);
 
 
     try {
     try {
-      const opt = this.injectGrowiUri(req, relation.growiUri);
+      this.injectGrowiUri(req, relation.growiUri);
+
+      const opt = req.body;
+      opt.headers = req.headers;
 
 
       await client.apiCall(method, opt);
       await client.apiCall(method, opt);
     }
     }

+ 28 - 0
packages/slackbot-proxy/src/interfaces/growi-uri-injector.ts

@@ -0,0 +1,28 @@
+export type GrowiUriWithOriginalData = {
+  growiUri: string,
+  originalData: string,
+}
+
+export type TypedBlock = {
+  type: string,
+}
+
+/**
+ * Type guard for GrowiUriWithOriginalData
+ * @param data
+ * @returns
+ */
+// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+export const isGrowiUriWithOriginalData = (data: any): data is GrowiUriWithOriginalData => {
+  return data.growiUri != null && data.originalData != null;
+};
+
+export interface GrowiUriInjector<ISDATA, IDATA, ESDATA, EDATA> {
+
+  shouldHandleToInject(data: ISDATA & any): data is IDATA;
+  inject(data: IDATA, growiUri:string): void;
+
+  shouldHandleToExtract(data: ESDATA & any): data is EDATA;
+  extract(data: EDATA): GrowiUriWithOriginalData;
+
+}

+ 20 - 23
packages/slackbot-proxy/src/middlewares/slack-to-growi/extract-growi-uri-from-req.ts

@@ -1,43 +1,40 @@
 import {
 import {
-  IMiddleware, Middleware, Next, Req, Res,
+  IMiddleware, Inject, Middleware, Next, Req, Res,
 } from '@tsed/common';
 } from '@tsed/common';
+
 import { SlackOauthReq } from '~/interfaces/slack-to-growi/slack-oauth-req';
 import { SlackOauthReq } from '~/interfaces/slack-to-growi/slack-oauth-req';
-import { growiUriInjectorFactory } from '~/services/growi-uri-injector/GrowiUriInjectorFactory';
-import { extractGrowiUriFromView } from '~/utils/extractGrowiUriFromView';
+import { ViewInteractionPayloadDelegator } from '~/services/growi-uri-injector/ViewInteractionPayloadDelegator';
+import { ActionsBlockPayloadDelegator } from '~/services/growi-uri-injector/ActionsBlockPayloadDelegator';
+
 
 
 @Middleware()
 @Middleware()
 export class ExtractGrowiUriFromReq implements IMiddleware {
 export class ExtractGrowiUriFromReq implements IMiddleware {
 
 
-  use(@Req() req: Req & SlackOauthReq, @Res() res: Res, @Next() next: Next): void {
+  @Inject()
+  viewInteractionPayloadDelegator: ViewInteractionPayloadDelegator;
+
+  @Inject()
+  actionsBlockPayloadDelegator: ActionsBlockPayloadDelegator;
+
+  use(@Req() req: SlackOauthReq, @Res() res: Res, @Next() next: Next): void {
 
 
     // There is no payload in the request from slack
     // There is no payload in the request from slack
     if (req.body.payload == null) {
     if (req.body.payload == null) {
       return next();
       return next();
     }
     }
 
 
-    const payload = JSON.parse(req.body.payload);
+    const parsedPayload = JSON.parse(req.body.payload);
 
 
-    // extract for modal
-    if (payload.view != null) {
-      const extractedValues = extractGrowiUriFromView(payload.view);
-      req.growiUri = extractedValues.growiUri;
-      payload.view.private_metadata = extractedValues.originalData;
+    if (this.viewInteractionPayloadDelegator.shouldHandleToExtract(parsedPayload)) {
+      const data = this.viewInteractionPayloadDelegator.extract(parsedPayload);
+      req.growiUri = data.growiUri;
     }
     }
-    else {
-      // break when uri is found
-      for (const type of Object.keys(growiUriInjectorFactory)) {
-        const growiUriInjector = growiUriInjectorFactory[type]();
-        const extractedValues = growiUriInjector.extract(payload.actions[0]);
-
-        if (extractedValues.growiUri != null) {
-          req.growiUri = extractedValues.growiUri;
-          payload.actions[0].value = JSON.stringify(extractedValues.originalData);
-          break;
-        }
-      }
+    else if (this.actionsBlockPayloadDelegator.shouldHandleToExtract(parsedPayload)) {
+      const data = this.actionsBlockPayloadDelegator.extract(parsedPayload);
+      req.growiUri = data.growiUri;
     }
     }
 
 
-    req.body.payload = JSON.stringify(payload);
+    req.body.payload = JSON.stringify(parsedPayload);
 
 
     return next();
     return next();
   }
   }

+ 84 - 0
packages/slackbot-proxy/src/services/growi-uri-injector/ActionsBlockPayloadDelegator.ts

@@ -0,0 +1,84 @@
+import { Inject, OnInit, Service } from '@tsed/di';
+import {
+  GrowiUriInjector, GrowiUriWithOriginalData, TypedBlock,
+} from '~/interfaces/growi-uri-injector';
+import { ButtonActionPayloadDelegator } from './block-elements/ButtonActionPayloadDelegator';
+import { CheckboxesActionPayloadDelegator } from './block-elements/CheckboxesActionPayloadDelegator';
+
+
+// see: https://api.slack.com/reference/block-kit/blocks
+type BlockElement = TypedBlock & {
+  elements: (TypedBlock & any)[],
+}
+
+// see: https://api.slack.com/reference/interaction-payloads/block-actions
+type BlockActionsPayload = TypedBlock & {
+  actions: TypedBlock[],
+}
+
+@Service()
+export class ActionsBlockPayloadDelegator implements GrowiUriInjector<any, BlockElement[], any, BlockActionsPayload>, OnInit {
+
+  @Inject()
+  buttonActionPayloadDelegator: ButtonActionPayloadDelegator;
+
+  @Inject()
+  checkboxesActionPayloadDelegator: CheckboxesActionPayloadDelegator;
+
+  private childDelegators: GrowiUriInjector<TypedBlock[], any, TypedBlock, any>[] = [];
+
+  $onInit(): void | Promise<any> {
+    this.childDelegators.push(
+      this.buttonActionPayloadDelegator,
+      this.checkboxesActionPayloadDelegator,
+    );
+  }
+
+  // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+  shouldHandleToInject(data: any): data is BlockElement[] {
+    const actionsBlocks = data.filter(blockElement => blockElement.type === 'actions');
+    return actionsBlocks.length > 0;
+  }
+
+  inject(data: BlockElement[], growiUri: string): void {
+    const actionsBlocks = data.filter(blockElement => blockElement.type === 'actions');
+
+    // collect elements
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const elements = actionsBlocks.flatMap(actionBlock => actionBlock.elements!);
+
+    this.childDelegators.forEach((delegator) => {
+      if (delegator.shouldHandleToInject(elements)) {
+        delegator.inject(elements, growiUri);
+      }
+    });
+  }
+
+  // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+  shouldHandleToExtract(data: any): data is BlockActionsPayload {
+    if (data.actions == null || data.actions.length === 0) {
+      return false;
+    }
+
+    const action = data.actions[0];
+    return this.childDelegators
+      .map(delegator => delegator.shouldHandleToExtract(action))
+      .includes(true);
+  }
+
+  extract(data: BlockActionsPayload): GrowiUriWithOriginalData {
+    let growiUriWithOriginalData: GrowiUriWithOriginalData;
+
+    const action = data.actions[0];
+    for (const delegator of this.childDelegators) {
+      if (delegator.shouldHandleToExtract(action)) {
+        growiUriWithOriginalData = delegator.extract(action);
+        break;
+      }
+    }
+
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    return growiUriWithOriginalData!;
+  }
+
+}

+ 0 - 19
packages/slackbot-proxy/src/services/growi-uri-injector/GrowiUriInjectionButtonDelegator.ts

@@ -1,19 +0,0 @@
-import { GrowiUriInjector } from './GrowiUriInjector';
-
-export class GrowiUriInjectionButtonDelegator implements GrowiUriInjector {
-
-  inject(element: {value:string}, growiUri:string): void {
-    const parsedValue = JSON.parse(element.value);
-    const originalData = JSON.stringify(parsedValue);
-    element.value = JSON.stringify({ growiUri, originalData });
-  }
-
-  extract(action: {value:string}): {growiUri?:string, originalData:any} {
-    const parsedValues = JSON.parse(action.value);
-    if (parsedValues.originalData != null) {
-      parsedValues.originalData = JSON.parse(parsedValues.originalData);
-    }
-    return parsedValues;
-  }
-
-}

+ 0 - 7
packages/slackbot-proxy/src/services/growi-uri-injector/GrowiUriInjector.ts

@@ -1,7 +0,0 @@
-
-export interface GrowiUriInjector {
-
-  inject(body: any, growiUri:string): void;
-
-  extract(body: any):any;
-}

+ 0 - 18
packages/slackbot-proxy/src/services/growi-uri-injector/GrowiUriInjectorFactory.ts

@@ -1,18 +0,0 @@
-import { GrowiUriInjector } from './GrowiUriInjector';
-import { GrowiUriInjectionButtonDelegator } from './GrowiUriInjectionButtonDelegator';
-
-/**
- * Instanciate GrowiUriInjector
- */
-export const growiUriInjectorFactory = {
-  button: (): GrowiUriInjector => {
-    return new GrowiUriInjectionButtonDelegator();
-  },
-};
-
-export const findInjectorByType = (type:string): null|GrowiUriInjector => {
-  if (!Object.keys(growiUriInjectorFactory).includes(type)) {
-    return null;
-  }
-  return growiUriInjectorFactory[type]();
-};

+ 62 - 0
packages/slackbot-proxy/src/services/growi-uri-injector/ViewInteractionPayloadDelegator.ts

@@ -0,0 +1,62 @@
+import { Service } from '@tsed/di';
+import {
+  GrowiUriInjector, GrowiUriWithOriginalData, isGrowiUriWithOriginalData, TypedBlock,
+} from '~/interfaces/growi-uri-injector';
+
+// see: https://api.slack.com/reference/interaction-payloads/views
+type ViewElement = TypedBlock & {
+  'private_metadata'?: any,
+}
+
+// see: https://api.slack.com/reference/interaction-payloads/views
+type ViewInteractionPayload = TypedBlock & {
+  view: {
+    'private_metadata'?: any,
+  },
+}
+
+@Service()
+export class ViewInteractionPayloadDelegator implements GrowiUriInjector<any, ViewElement, any, ViewInteractionPayload> {
+
+  // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+  shouldHandleToInject(data: any): data is ViewElement {
+    return data.type != null && data.private_metadata != null;
+  }
+
+  inject(data: ViewElement, growiUri :string): void {
+    const originalData = data.private_metadata;
+
+    const urlWithOrgData: GrowiUriWithOriginalData = { growiUri, originalData };
+
+    data.private_metadata = JSON.stringify(urlWithOrgData);
+  }
+
+  // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+  shouldHandleToExtract(data: any): data is ViewInteractionPayload {
+    const { type, view } = data;
+    if (type !== 'view_submission') {
+      return false;
+    }
+    if (view.private_metadata == null) {
+      return false;
+    }
+
+    try {
+      const restoredData: any = JSON.parse(view.private_metadata);
+      return isGrowiUriWithOriginalData(restoredData);
+    }
+    // when parsing failed
+    catch (err) {
+      return false;
+    }
+  }
+
+  extract(data: ViewInteractionPayload): GrowiUriWithOriginalData {
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const restoredData: GrowiUriWithOriginalData = JSON.parse(data.view.private_metadata!); // private_metadata must not be null at this moment
+    data.view.private_metadata = restoredData.originalData;
+
+    return restoredData;
+  }
+
+}

+ 43 - 0
packages/slackbot-proxy/src/services/growi-uri-injector/block-elements/ButtonActionPayloadDelegator.ts

@@ -0,0 +1,43 @@
+import { Service } from '@tsed/di';
+import { GrowiUriWithOriginalData, GrowiUriInjector, TypedBlock } from '~/interfaces/growi-uri-injector';
+
+
+type ButtonElement = TypedBlock & {
+  value: string,
+}
+
+type ButtonActionPayload = TypedBlock & {
+  value: string,
+}
+
+@Service()
+export class ButtonActionPayloadDelegator implements GrowiUriInjector<TypedBlock[], ButtonElement[], TypedBlock, ButtonActionPayload> {
+
+  shouldHandleToInject(elements: TypedBlock[]): elements is ButtonElement[] {
+    const buttonElements = elements.filter(element => element.type === 'button');
+    return buttonElements.length > 0;
+  }
+
+  inject(elements: ButtonElement[], growiUri: string): void {
+    const buttonElements = elements.filter(blockElement => blockElement.type === 'button');
+
+    buttonElements
+      .forEach((element) => {
+        const urlWithOrgData: GrowiUriWithOriginalData = { growiUri, originalData: element.value };
+        element.value = JSON.stringify(urlWithOrgData);
+      });
+  }
+
+  shouldHandleToExtract(action: TypedBlock): action is ButtonActionPayload {
+    return action.type === 'button';
+  }
+
+  extract(action: ButtonActionPayload): GrowiUriWithOriginalData {
+    const restoredData: GrowiUriWithOriginalData = JSON.parse(action.value);
+    action.value = restoredData.originalData;
+
+    return restoredData;
+  }
+
+
+}

+ 57 - 0
packages/slackbot-proxy/src/services/growi-uri-injector/block-elements/CheckboxesActionPayloadDelegator.ts

@@ -0,0 +1,57 @@
+import { Service } from '@tsed/di';
+import { GrowiUriWithOriginalData, GrowiUriInjector, TypedBlock } from '~/interfaces/growi-uri-injector';
+
+
+type CheckboxesElement = TypedBlock & {
+  options: { value: string }[],
+}
+
+type CheckboxesActionPayload = TypedBlock & {
+  'selected_options': { value: string }[],
+}
+
+@Service()
+export class CheckboxesActionPayloadDelegator implements GrowiUriInjector<TypedBlock[], CheckboxesElement[], TypedBlock, CheckboxesActionPayload> {
+
+  shouldHandleToInject(elements: TypedBlock[]): elements is CheckboxesElement[] {
+    const buttonElements = elements.filter(element => element.type === 'checkboxes');
+    return buttonElements.length > 0;
+  }
+
+  inject(elements: CheckboxesElement[], growiUri: string): void {
+    const cbElements = elements.filter(blockElement => blockElement.type === 'checkboxes');
+
+    cbElements.forEach((element) => {
+      element.options.forEach((option) => {
+        const urlWithOrgData: GrowiUriWithOriginalData = { growiUri, originalData: option.value };
+        option.value = JSON.stringify(urlWithOrgData);
+      });
+    });
+  }
+
+  shouldHandleToExtract(action: TypedBlock): action is CheckboxesActionPayload {
+    return (
+      action.type === 'checkboxes'
+      && (action as CheckboxesActionPayload).selected_options != null
+      // ...Unsolved problem...
+      // slackbot-proxy can't determine growiUri when selected_options is empty -- 2021.07.12 Yuki Takei
+      && (action as CheckboxesActionPayload).selected_options.length > 0
+    );
+  }
+
+  extract(action: CheckboxesActionPayload): GrowiUriWithOriginalData {
+    let oneOfRestoredData: GrowiUriWithOriginalData;
+
+    action.selected_options.forEach((selectedOption) => {
+      const restoredData = JSON.parse(selectedOption.value);
+      selectedOption.value = restoredData.originalData;
+
+      // update oneOfRestoredData
+      oneOfRestoredData = restoredData;
+    });
+
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    return oneOfRestoredData!;
+  }
+
+}

+ 0 - 10
packages/slackbot-proxy/src/utils/extractGrowiUriFromView.ts

@@ -1,10 +0,0 @@
-export const extractGrowiUriFromView = (view:{'private_metadata': string}): {growiUri?:string, originalData:{[key:string]:any}} => {
-  const parsedValues = JSON.parse(view.private_metadata);
-  if (parsedValues.originalData != null) {
-    parsedValues.originalData = JSON.parse(parsedValues.originalData);
-  }
-  else {
-    parsedValues.originalData = view.private_metadata;
-  }
-  return parsedValues;
-};

+ 0 - 7
packages/slackbot-proxy/src/utils/injectGrowiUriToView.ts

@@ -1,7 +0,0 @@
-export const injectGrowiUriToView = (body: {view:string}, growiUri:string): void => {
-  const parsedView = JSON.parse(body.view);
-  const originalData = JSON.stringify(parsedView.private_metadata);
-
-  parsedView.private_metadata = JSON.stringify({ growiUri, originalData });
-  body.view = JSON.stringify(parsedView);
-};

+ 98 - 92
packages/slackbot-proxy/src/views/privacy.ejs

@@ -1,95 +1,101 @@
 <%- include('commons/head'); %>
 <%- include('commons/head'); %>
 
 
-<body style="max-width: 600px; padding-top:100px; margin: 0 auto;">
-  <h1 style="text-align:center;">Privacy Policy</h1>
-  <h2 style="text-align:center;">At First</h2>
-  <p>
-    Your privacy is critically important to us. At GROWI Official Bot we have a few fundamental principles:
-  </p>
-  <ul>
-    <li>We don’t ask you for personal information unless we truly need it.</li>
-    <li>We don’t share your personal information with anyone except to comply with the law, develop our products, or protect our rights.</li>
-    <li>We don’t store personal information on our servers unless required for the on-going operation of the service.</li>
-  </ul>
-  <p>
-    If you have questions about deleting or correcting your personal data please contact support.
-  </p>
-  <p>
-    WESEEK, Inc. operates slack bot about GROWI. – henceforth referred to as "GROWI Official Bot". It is slack bot’s policy to respect your privacy regarding any information we may collect while operating our service.
-  </p>
-  <h2 style="text-align:center;">What Personal Data Do We Receive?</h2>
-  <p>
-    Personal information is information about an identified or identifiable individual, or about an identifiable individual, including information that WESEEK, Inc. can associate with an individual.
-  </p>
-  <p>
-    When using or operating the GROWI Official Bot, we may collect or process the following categories of personal information on your behalf.
-  </p>
-  <h2 style="text-align:center;">Protection of specific personal information</h2>
-  <p>
-    WESEEK, Inc. provides potentially personally identifiable information and personally identifiable information.
-  </p>
-  <ul>
-    <li>
-      We will only disclose it to the information of employees, contractors, and related organizations who need to know that information to process on behalf of WESEEK, Inc. or to provide the services available on the GROWI Official Bot.
-    </li>
-    <li>
-      Those who have agreed not to disclose it to others. Some of these employees, contractors, and related organizations may be located outside of their home country.
-    </li>
-  </ul>
-  <p>
-    By using GROWI Official Bot, you agree to transfer such information to them. As mentioned above, other than employees, contractors, and related organizations, WESEEK, Inc. does not lend or sell personally identifiable or personally identifiable information to third parties.
-  </p>
-  <p>
-    WESEEK, Inc. will take all reasonable steps to protect personally identifiable information and personally identifiable information from unauthorized access, use, modification or destruction.
-  </p>
-  <h2 style="text-align:center;">Other information to collect</h2>
-  <p>
-    order to enable mutual communication between your GROWI and Slack, we may collect, retain and process the following information that does not fall within the definition of personal information.
-  </p>
-  <ul>
-    <li>
-      Slack workspace information
-      <ul>
-        <li>
-          Includes workspace name, team ID, bot token associated with the workspace, and more.
-        </li>
-      </ul>
-    </li>
-    <li>
-      GROWI information
-      <ul>
-        <li>
-          Includes GROWI URIs for communicating with Slack, access tokens, and more.
-        </li>
-      </ul>
-    </li>
-    <li>
-      Information about communication
-      <ul>
-        <li>
-          Contains information about communication between Slack and GROWI.
-        </li>
-      </ul>
-    </li>
-  </ul>
-  <h2 style="text-align:center;">
-    Business Transfers
-  </h2>
-  <p>
-    If WESEEK, Inc. or substantially all of its assets, were acquired, or in the unlikely event that WESEEK, Inc.
-    goes out of business or enters bankruptcy, user information would be one of the assets that is transferred or acquired by a third party.
-  </p>
-  <p>
-    You acknowledge that such transfers may occur, and that any acquirer of WESEEK, Inc. may continue to use your personal information as set forth in this policy.
-  </p>
-  <h2 style="text-align:center;">
-    Privacy Policy Changes
-  </h2>
-  <p>
-    Although most changes are likely to be minor, WESEEK, Inc. may change its Privacy Policy from time to time, and in WESEEK, Inc.’s sole discretion.
-    WESEEK, Inc. encourages visitors to frequently check this page for any changes to its Privacy Policy.
-  </p>
-  <p>
-    Your continued use of this site after any change in this Privacy Policy will constitute your acceptance of such change.
-  </p>
+<body>
+  <div class="row">
+    <div class="col-12 col-md-6 offset-md-3">
+      <div class="p-3">
+        <h1 class="text-center">Privacy Policy</h1>
+        <h2 class="text-center"">At First</h2>
+        <p>
+          Your privacy is critically important to us. At GROWI Official Bot we have a few fundamental principles:
+        </p>
+        <ul>
+          <li>We don’t ask you for personal information unless we truly need it.</li>
+          <li>We don’t share your personal information with anyone except to comply with the law, develop our products, or protect our rights.</li>
+          <li>We don’t store personal information on our servers unless required for the on-going operation of the service.</li>
+        </ul>
+        <p>
+          If you have questions about deleting or correcting your personal data please contact support.
+        </p>
+        <p>
+          WESEEK, Inc. operates slack bot about GROWI. – henceforth referred to as "GROWI Official Bot". It is slack bot’s policy to respect your privacy regarding any information we may collect while operating our service.
+        </p>
+        <h2 class="text-center"">What Personal Data Do We Receive?</h2>
+        <p>
+          Personal information is information about an identified or identifiable individual, or about an identifiable individual, including information that WESEEK, Inc. can associate with an individual.
+        </p>
+        <p>
+          When using or operating the GROWI Official Bot, we may collect or process the following categories of personal information on your behalf.
+        </p>
+        <h2 class="text-center"">Protection of specific personal information</h2>
+        <p>
+          WESEEK, Inc. provides potentially personally identifiable information and personally identifiable information.
+        </p>
+        <ul>
+          <li>
+            We will only disclose it to the information of employees, contractors, and related organizations who need to know that information to process on behalf of WESEEK, Inc. or to provide the services available on the GROWI Official Bot.
+          </li>
+          <li>
+            Those who have agreed not to disclose it to others. Some of these employees, contractors, and related organizations may be located outside of their home country.
+          </li>
+        </ul>
+        <p>
+          By using GROWI Official Bot, you agree to transfer such information to them. As mentioned above, other than employees, contractors, and related organizations, WESEEK, Inc. does not lend or sell personally identifiable or personally identifiable information to third parties.
+        </p>
+        <p>
+          WESEEK, Inc. will take all reasonable steps to protect personally identifiable information and personally identifiable information from unauthorized access, use, modification or destruction.
+        </p>
+        <h2 class="text-center"">Other information to collect</h2>
+        <p>
+          order to enable mutual communication between your GROWI and Slack, we may collect, retain and process the following information that does not fall within the definition of personal information.
+        </p>
+        <ul>
+          <li>
+            Slack workspace information
+            <ul>
+              <li>
+                Includes workspace name, team ID, bot token associated with the workspace, and more.
+              </li>
+            </ul>
+          </li>
+          <li>
+            GROWI information
+            <ul>
+              <li>
+                Includes GROWI URIs for communicating with Slack, access tokens, and more.
+              </li>
+            </ul>
+          </li>
+          <li>
+            Information about communication
+            <ul>
+              <li>
+                Contains information about communication between Slack and GROWI.
+              </li>
+            </ul>
+          </li>
+        </ul>
+        <h2 class="text-center"">
+          Business Transfers
+        </h2>
+        <p>
+          If WESEEK, Inc. or substantially all of its assets, were acquired, or in the unlikely event that WESEEK, Inc.
+          goes out of business or enters bankruptcy, user information would be one of the assets that is transferred or acquired by a third party.
+        </p>
+        <p>
+          You acknowledge that such transfers may occur, and that any acquirer of WESEEK, Inc. may continue to use your personal information as set forth in this policy.
+        </p>
+        <h2 class="text-center"">
+          Privacy Policy Changes
+        </h2>
+        <p>
+          Although most changes are likely to be minor, WESEEK, Inc. may change its Privacy Policy from time to time, and in WESEEK, Inc.’s sole discretion.
+          WESEEK, Inc. encourages visitors to frequently check this page for any changes to its Privacy Policy.
+        </p>
+        <p>
+          Your continued use of this site after any change in this Privacy Policy will constitute your acceptance of such change.
+        </p>
+      </div>
+    </div>
+  </div>
 </body>
 </body>

+ 70 - 64
packages/slackbot-proxy/src/views/term.ejs

@@ -1,79 +1,85 @@
 <%- include('commons/head'); %>
 <%- include('commons/head'); %>
 
 
-<body class="pt-5 m-3 w-50 mx-auto d-flex flex-column">
-  <h1 class="text-center">Terms of Service </h1>
-    <h2 class="text-center">At First</h2>
-      <p>This Terms of Use Agreement(hereinafter referred to as the "Agreement") stipulates the terms and conditions of use for the services provided by WESEEK,Inc. on GROWI Slack-Bot(hereinafter referred to as the "Services"). All registered users(hereinafter referred to as "Users") are required to follow these Terms of Service.</p>
+<body>
+  <div class="row">
+    <div class="col-12 col-md-6 offset-md-3">
+      <div class="p-3">
+        <h1 class="text-center">Terms of Service</h1>
+        <h2 class="text-center">At First</h2>
+        <p>This Terms of Use Agreement(hereinafter referred to as the "Agreement") stipulates the terms and conditions of use for the services provided by WESEEK,Inc. on GROWI Slack-Bot(hereinafter referred to as the "Services"). All registered users(hereinafter referred to as "Users") are required to follow these Terms of Service.</p>
 
 
-  <h2 class="text-center">Application of Terms</h2>
-    <p>This Agreement shall apply to all relationships between the User and WESEEK,Inc. regarding the use of the Service.  In addition to this Agreement, WESEEK,Inc. may make various provisions regarding the Service, such as rules for use (hereinafter referred to as "Individual Provisions"). These individual regulations may be called by any name. These Individual Regulations, regardless of their names, shall constitute a part of these Terms. In the event that the provisions of these Terms of Use conflict with the provisions of the Individual Provisions of the preceding article, the provisions of the Individual Provisions shall take precedence unless otherwise specified in the Individual Provisions.</p>
+        <h2 class="text-center">Application of Terms</h2>
+        <p>This Agreement shall apply to all relationships between the User and WESEEK,Inc. regarding the use of the Service.  In addition to this Agreement, WESEEK,Inc. may make various provisions regarding the Service, such as rules for use (hereinafter referred to as "Individual Provisions"). These individual regulations may be called by any name. These Individual Regulations, regardless of their names, shall constitute a part of these Terms. In the event that the provisions of these Terms of Use conflict with the provisions of the Individual Provisions of the preceding article, the provisions of the Individual Provisions shall take precedence unless otherwise specified in the Individual Provisions.</p>
 
 
-  <h2 class="text-center">User Registration</h2>
-   <p>Registration for this service shall be completed when the applicant agrees to these Terms of Use, applies for registration in accordance with the method specified by WESEEK,Inc. approves the application. If WESEEK,Inc. determines that the applicant has any of the following reasons, WESEEK,Inc. may not approve the application for registration, and WESEEK,Inc. shall not be obligated to disclose any of the reasons.</p>
-    <ul>
-      <li>If the applicant has provided false information in the application for registration.</li>
-      <li>If the application is from a person who has violated this agreement.</li>
-      <li>In addition, when WESEEK,Inc. deems that the registration is not appropriate.</li>
-    </ul>
+        <h2 class="text-center">User Registration</h2>
+        <p>Registration for this service shall be completed when the applicant agrees to these Terms of Use, applies for registration in accordance with the method specified by WESEEK,Inc. approves the application. If WESEEK,Inc. determines that the applicant has any of the following reasons, WESEEK,Inc. may not approve the application for registration, and WESEEK,Inc. shall not be obligated to disclose any of the reasons.</p>
+          <ul>
+            <li>If the applicant has provided false information in the application for registration.</li>
+            <li>If the application is from a person who has violated this agreement.</li>
+            <li>In addition, when WESEEK,Inc. deems that the registration is not appropriate.</li>
+          </ul>
 
 
-  <h2 class="text-center">Account</h2>
-    <p>Users are responsible for maintaining the privacy and security of their accounts. We will not be liable for any damage or loss caused by your failure to protect your login information, including your password.</p>
+        <h2 class="text-center">Account</h2>
+        <p>Users are responsible for maintaining the privacy and security of their accounts. We will not be liable for any damage or loss caused by your failure to protect your login information, including your password.</p>
 
 
-  <h2 class="text-center">Prohibited Matters</h2>
-    <p>Users shall not commit any of the following acts when using the Service.</p>
-    <ul>
-      <li>Acts that violate laws or public order and morals</li>
-      <li>Acts related to criminal acts</li>
-      <li>Acts that destroy or interfere with the functions of the servers or networks of WESEEK,Inc., other users, or other third parties.</li>
-      <li>Acts that may interfere with the operation of WESEEK,Inc.'s services.</li>
-      <li>Unauthorized access or attempts to do so.</li>
-      <li>Acts that collect or accumulate personal information about other users.</li>
-      <li>Acts of using this service for illegal purposes.</li>
-      <li>Actions that cause disadvantage, damage, or discomfort to other users of this service or other third parties.</li>
-      <li>Act of impersonating other users.</li>
-      <li>Advertising, solicitation, or business activities on this service that are not authorized by WESEEK,Inc..</li>
-      <li>Directly or indirectly providing benefits to antisocial forces in relation to WESEEK,Inc.'s services.</li>
-      <li>Any other acts that WESEEK,Inc. deems inappropriate.</li>
-    </ul>
+        <h2 class="text-center">Prohibited Matters</h2>
+        <p>Users shall not commit any of the following acts when using the Service.</p>
+        <ul>
+          <li>Acts that violate laws or public order and morals</li>
+          <li>Acts related to criminal acts</li>
+          <li>Acts that destroy or interfere with the functions of the servers or networks of WESEEK,Inc., other users, or other third parties.</li>
+          <li>Acts that may interfere with the operation of WESEEK,Inc.'s services.</li>
+          <li>Unauthorized access or attempts to do so.</li>
+          <li>Acts that collect or accumulate personal information about other users.</li>
+          <li>Acts of using this service for illegal purposes.</li>
+          <li>Actions that cause disadvantage, damage, or discomfort to other users of this service or other third parties.</li>
+          <li>Act of impersonating other users.</li>
+          <li>Advertising, solicitation, or business activities on this service that are not authorized by WESEEK,Inc..</li>
+          <li>Directly or indirectly providing benefits to antisocial forces in relation to WESEEK,Inc.'s services.</li>
+          <li>Any other acts that WESEEK,Inc. deems inappropriate.</li>
+        </ul>
 
 
-  <h2 class="text-center">Suspension of Provision of the Service, etc.</h2>
-    <p>WESEEK,Inc. may suspend or interrupt the provision of all or part of the Service without prior notice to the User if WESEEK,Inc. deems any of the following to be the case When performing maintenance, inspection, or updating of the computer system for this service</p>
-    <ul>
-      <li>In the event that the provision of the Service becomes difficult due to force majeure such as earthquakes, lightning, fire, power outages, or natural disasters.</li>
-      <li>When a computer or communication line is stopped due to an accident</li>
-      <li>In any other cases where WESEEK,Inc. deems it difficult to provide the Service.</li>
-    </ul>
-    <p>WESEEK,Inc. shall not be liable for any disadvantage or damage incurred by the User or any third party due to the suspension or interruption of the provision of the Service.</p>
+        <h2 class="text-center">Suspension of Provision of the Service, etc.</h2>
+        <p>WESEEK,Inc. may suspend or interrupt the provision of all or part of the Service without prior notice to the User if WESEEK,Inc. deems any of the following to be the case When performing maintenance, inspection, or updating of the computer system for this service</p>
+        <ul>
+          <li>In the event that the provision of the Service becomes difficult due to force majeure such as earthquakes, lightning, fire, power outages, or natural disasters.</li>
+          <li>When a computer or communication line is stopped due to an accident</li>
+          <li>In any other cases where WESEEK,Inc. deems it difficult to provide the Service.</li>
+        </ul>
+        <p>WESEEK,Inc. shall not be liable for any disadvantage or damage incurred by the User or any third party due to the suspension or interruption of the provision of the Service.</p>
 
 
-  <h2 class="text-center">Restriction of Use and Cancellation of Registration</h2>
-    <p>In the event that a User falls under any of the following, WESEEK,Inc. may, without prior notice, restrict the User from using all or part of the Service, or cancel the User's registration.</P>
-    <ul>
-      <li>If the user violates any of the provisions of this agreement.</li>
-      <li>When it is found that there is a false fact in the registered information.</li>
-      <li>When there is no response to communication from WESEEK,Inc. for a certain period of time.</li>
-      <li>When there is no use of this service for a certain period of time after the last use.</li>
-      <li>In any other cases where WESEEK,Inc. deems the use of the Service to be inappropriate.</li>
-    </ul>
-    <p>WESEEK,Inc. shall not be liable for any damages incurred by the User as a result of any action taken by WESEEK,Inc. in accordance with this Article.</p>
+        <h2 class="text-center">Restriction of Use and Cancellation of Registration</h2>
+        <p>In the event that a User falls under any of the following, WESEEK,Inc. may, without prior notice, restrict the User from using all or part of the Service, or cancel the User's registration.</P>
+        <ul>
+          <li>If the user violates any of the provisions of this agreement.</li>
+          <li>When it is found that there is a false fact in the registered information.</li>
+          <li>When there is no response to communication from WESEEK,Inc. for a certain period of time.</li>
+          <li>When there is no use of this service for a certain period of time after the last use.</li>
+          <li>In any other cases where WESEEK,Inc. deems the use of the Service to be inappropriate.</li>
+        </ul>
+        <p>WESEEK,Inc. shall not be liable for any damages incurred by the User as a result of any action taken by WESEEK,Inc. in accordance with this Article.</p>
 
 
-  <h2 class="text-center">No Warranty and Disclaimer</h2>
-    <p>WESEEK,Inc does not warrant, expressly or impliedly, that the Service is free from defects in fact or in law (including defects in safety, reliability, accuracy, completeness, effectiveness, fitness for a particular purpose, security, errors or bugs, infringement of rights, etc.).
-      WESEEK,Inc shall not be liable for any damages incurred by the User due to the Service. WESEEK,Inc shall not be liable for any transactions, communications, or disputes that may arise between the User and other users or third parties in relation to the Service.</p>
-  <h2 class="text-center">Modification of Service Contents, etc.</h2>
-    <p>WESEEK,Inc. may change the contents of the Service or discontinue the provision of the Service without notice to the User, and shall not be liable for any damages incurred by the User as a result of such changes.</p>
+        <h2 class="text-center">No Warranty and Disclaimer</h2>
+        <p>WESEEK,Inc does not warrant, expressly or impliedly, that the Service is free from defects in fact or in law (including defects in safety, reliability, accuracy, completeness, effectiveness, fitness for a particular purpose, security, errors or bugs, infringement of rights, etc.).
+            WESEEK,Inc shall not be liable for any damages incurred by the User due to the Service. WESEEK,Inc shall not be liable for any transactions, communications, or disputes that may arise between the User and other users or third parties in relation to the Service.</p>
+        <h2 class="text-center">Modification of Service Contents, etc.</h2>
+        <p>WESEEK,Inc. may change the contents of the Service or discontinue the provision of the Service without notice to the User, and shall not be liable for any damages incurred by the User as a result of such changes.</p>
 
 
-  <h2 class="text-center">Modification of the Terms of Service, etx.</h2>
-    <p>WESEEK,Inc. may change the Terms of Use at any time without notice to the User, if WESEEK,Inc. deems it necessary. In the event that a User begins to use the Service after a change to the Terms, the User shall be deemed to have agreed to the changed Terms.</p>
+        <h2 class="text-center">Modification of the Terms of Service, etx.</h2>
+        <p>WESEEK,Inc. may change the Terms of Use at any time without notice to the User, if WESEEK,Inc. deems it necessary. In the event that a User begins to use the Service after a change to the Terms, the User shall be deemed to have agreed to the changed Terms.</p>
 
 
-  <h2 class="text-center">Handling of Personal Information</h2>
-    <p>WESEEK,Inc. shall handle personal information obtained through the use of the Service in an appropriate manner in accordance with WESEEK,Inc.'s "Privacy Policy".</p>
+        <h2 class="text-center">Handling of Personal Information</h2>
+        <p>WESEEK,Inc. shall handle personal information obtained through the use of the Service in an appropriate manner in accordance with WESEEK,Inc.'s "Privacy Policy".</p>
 
 
-  <h2 class="text-center">Notification or Communication</h2>
-    <p>Notification or communication between the User and WESEEK,Inc. shall be conducted in a manner determined by WESEEK,Inc.. Unless the User notifies WESEEK,Inc. of a change in the method specified separately by WESEEK,Inc., WESEEK,Inc. will assume that the currently registered contact information is valid and send notifications or communications to said contact information, and these notifications or communications will be deemed to have reached the User at the time they are sent.</p>
+        <h2 class="text-center">Notification or Communication</h2>
+        <p>Notification or communication between the User and WESEEK,Inc. shall be conducted in a manner determined by WESEEK,Inc.. Unless the User notifies WESEEK,Inc. of a change in the method specified separately by WESEEK,Inc., WESEEK,Inc. will assume that the currently registered contact information is valid and send notifications or communications to said contact information, and these notifications or communications will be deemed to have reached the User at the time they are sent.</p>
 
 
-  <h2 class="text-center">Prohibition of Transfer of Rights and Obligations</h2>
-    <p>The User may not assign or pledge to a third party his/her position in the User Agreement or rights or obligations under this Agreement without the prior written consent of WESEEK,Inc..</p>
+        <h2 class="text-center">Prohibition of Transfer of Rights and Obligations</h2>
+        <p>The User may not assign or pledge to a third party his/her position in the User Agreement or rights or obligations under this Agreement without the prior written consent of WESEEK,Inc..</p>
 
 
-  <h2 class="text-center">Governing Law</h2>
-    <p>These Terms of Use shall be governed by and construed in accordance with the laws of Japan. In the event of a dispute regarding the Service, the court having jurisdiction over the location of the head office of WESEEK,Inc. shall have exclusive jurisdiction.</p>
+        <h2 class="text-center">Governing Law</h2>
+        <p>These Terms of Use shall be governed by and construed in accordance with the laws of Japan. In the event of a dispute regarding the Service, the court having jurisdiction over the location of the head office of WESEEK,Inc. shall have exclusive jurisdiction.</p>
+      </div>
+    </div>
+  </div>
 </body>
 </body>