Compare commits
	
		
			4 Commits 
		
	
	
		
			0048fa483f
			...
			ea163b62b4
		
	
	| Author | SHA1 | Date | 
|---|---|---|
| 
							
							
								 | 
						ea163b62b4 | |
| 
							
							
								 | 
						c4e5608aca | |
| 
							
							
								 | 
						f4f5dad8d2 | |
| 
							
							
								 | 
						05c9f3af6d | 
| 
						 | 
					@ -22,6 +22,7 @@
 | 
				
			||||||
    "@types/pino": "^6.3.4",
 | 
					    "@types/pino": "^6.3.4",
 | 
				
			||||||
    "axios": "^1.2.3",
 | 
					    "axios": "^1.2.3",
 | 
				
			||||||
    "bcryptjs": "^2.4.3",
 | 
					    "bcryptjs": "^2.4.3",
 | 
				
			||||||
 | 
					    "compression": "^1.7.4",
 | 
				
			||||||
    "cookie-parser": "^1.4.5",
 | 
					    "cookie-parser": "^1.4.5",
 | 
				
			||||||
    "cors": "^2.8.5",
 | 
					    "cors": "^2.8.5",
 | 
				
			||||||
    "date-fns": "^2.30.0",
 | 
					    "date-fns": "^2.30.0",
 | 
				
			||||||
| 
						 | 
					@ -56,9 +57,9 @@
 | 
				
			||||||
    "yup": "^0.32.8"
 | 
					    "yup": "^0.32.8"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "@types/lodash": "4.14",
 | 
					 | 
				
			||||||
    "@types/bcryptjs": "^2.4.2",
 | 
					    "@types/bcryptjs": "^2.4.2",
 | 
				
			||||||
    "@types/bluebird": "^3.5.32",
 | 
					    "@types/bluebird": "^3.5.32",
 | 
				
			||||||
 | 
					    "@types/compression": "^1.7.5",
 | 
				
			||||||
    "@types/cookie-parser": "^1.4.2",
 | 
					    "@types/cookie-parser": "^1.4.2",
 | 
				
			||||||
    "@types/cors": "^2.8.7",
 | 
					    "@types/cors": "^2.8.7",
 | 
				
			||||||
    "@types/express": "^4.17.13",
 | 
					    "@types/express": "^4.17.13",
 | 
				
			||||||
| 
						 | 
					@ -66,6 +67,7 @@
 | 
				
			||||||
    "@types/faker": "^5.1.3",
 | 
					    "@types/faker": "^5.1.3",
 | 
				
			||||||
    "@types/jest": "^26.0.15",
 | 
					    "@types/jest": "^26.0.15",
 | 
				
			||||||
    "@types/jsonwebtoken": "^8.5.0",
 | 
					    "@types/jsonwebtoken": "^8.5.0",
 | 
				
			||||||
 | 
					    "@types/lodash": "4.14",
 | 
				
			||||||
    "@types/multer": "^1.4.4",
 | 
					    "@types/multer": "^1.4.4",
 | 
				
			||||||
    "@types/node": "^14.11.8",
 | 
					    "@types/node": "^14.11.8",
 | 
				
			||||||
    "@types/supertest": "^2.0.10",
 | 
					    "@types/supertest": "^2.0.10",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,6 +11,7 @@ import uploadConfig from "./config/upload";
 | 
				
			||||||
import AppError from "./errors/AppError";
 | 
					import AppError from "./errors/AppError";
 | 
				
			||||||
import routes from "./routes";
 | 
					import routes from "./routes";
 | 
				
			||||||
import { logger } from "./utils/logger";  
 | 
					import { logger } from "./utils/logger";  
 | 
				
			||||||
 | 
					import compression from 'compression';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Sentry.init({ dsn: process.env.SENTRY_DSN });
 | 
					Sentry.init({ dsn: process.env.SENTRY_DSN });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,6 +24,7 @@ app.use(
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
); 
 | 
					); 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.use(compression());
 | 
				
			||||||
app.use(cookieParser());
 | 
					app.use(cookieParser());
 | 
				
			||||||
app.use(express.json());
 | 
					app.use(express.json());
 | 
				
			||||||
app.use(Sentry.Handlers.requestHandler());
 | 
					app.use(Sentry.Handlers.requestHandler());
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,6 @@
 | 
				
			||||||
import { getIO } from "../../libs/socket";
 | 
					import { getIO } from "../../libs/socket";
 | 
				
			||||||
import Contact from "../../models/Contact";
 | 
					import Contact from "../../models/Contact";
 | 
				
			||||||
 | 
					const { Op } = require('sequelize');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { createOrUpdateContactCache } from '../../helpers/ContactsCache'
 | 
					import { createOrUpdateContactCache } from '../../helpers/ContactsCache'
 | 
				
			||||||
import { tr } from "date-fns/locale";
 | 
					import { tr } from "date-fns/locale";
 | 
				
			||||||
| 
						 | 
					@ -35,15 +36,37 @@ const CreateOrUpdateContactService = async ({
 | 
				
			||||||
    const io = getIO();
 | 
					    const io = getIO();
 | 
				
			||||||
    let contact: Contact | null;
 | 
					    let contact: Contact | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const firstFourDigits = number.slice(0, 4);
 | 
				
			||||||
 | 
					    const lastEightDigits = number.slice(-8);
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    contact = await Contact.findOne({ where: { number } });
 | 
					    //const numberFormat = number?.length === 13 && number[4] == '9' ? number.slice(0, 4) + number.slice(0, 4) : number;
 | 
				
			||||||
 | 
					    //contact = await Contact.findOne({ where: { number } });
 | 
				
			||||||
 | 
					    contact = await Contact.findOne({
 | 
				
			||||||
 | 
					      where: {
 | 
				
			||||||
 | 
					        [Op.and]: [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            number: {
 | 
				
			||||||
 | 
					              [Op.like]: `%${firstFourDigits}%`
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            number: {
 | 
				
			||||||
 | 
					              [Op.like]: `%${lastEightDigits}%`
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (contact) {
 | 
					    if (contact) {
 | 
				
			||||||
      contact.update({ profilePicUrl });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // TEST DEL
 | 
					      if(contact.number == number){
 | 
				
			||||||
 | 
					        contact.update({ profilePicUrl });
 | 
				
			||||||
        await createOrUpdateContactCache(`contact:${contact.id}`, { profilePicUrl })
 | 
					        await createOrUpdateContactCache(`contact:${contact.id}`, { profilePicUrl })
 | 
				
			||||||
      //
 | 
					      } else{ 
 | 
				
			||||||
 | 
					        contact.update({ profilePicUrl, number });
 | 
				
			||||||
 | 
					        await createOrUpdateContactCache(`contact:${contact.id}`, { profilePicUrl, number })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      io.emit("contact", {
 | 
					      io.emit("contact", {
 | 
				
			||||||
        action: "update",
 | 
					        action: "update",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,9 +12,6 @@ import UpdateTicketService from "./UpdateTicketService";
 | 
				
			||||||
import { getSettingValue } from "../../helpers/WhaticketSettings"
 | 
					import { getSettingValue } from "../../helpers/WhaticketSettings"
 | 
				
			||||||
import ListWhatsAppsNumber from "../WhatsappService/ListWhatsAppsNumber"
 | 
					import ListWhatsAppsNumber from "../WhatsappService/ListWhatsAppsNumber"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { getSettingValue } from "../../helpers/WhaticketSettings";
 | 
					 | 
				
			||||||
import ListWhatsAppsNumber from "../WhatsappService/ListWhatsAppsNumber";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const FindOrCreateTicketServiceBot = async (
 | 
					const FindOrCreateTicketServiceBot = async (
 | 
				
			||||||
  contact: Contact,
 | 
					  contact: Contact,
 | 
				
			||||||
  whatsappId: number,
 | 
					  whatsappId: number,
 | 
				
			||||||
| 
						 | 
					@ -104,8 +101,6 @@ const FindOrCreateTicketServiceBot = async (
 | 
				
			||||||
          unreadMessages
 | 
					          unreadMessages
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        console.log("lxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await dialogFlowStartContext(contact, ticket, botInfo);
 | 
					        await dialogFlowStartContext(contact, ticket, botInfo);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -131,8 +126,6 @@ const FindOrCreateTicketServiceBot = async (
 | 
				
			||||||
        phoneNumberId
 | 
					        phoneNumberId
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      console.log("yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      await dialogFlowStartContext(contact, ticket, botInfo);
 | 
					      await dialogFlowStartContext(contact, ticket, botInfo);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1061,8 +1061,6 @@ const handleMessage = async (
 | 
				
			||||||
    // console.log('----------> chat: ', JSON.parse(JSON.stringify(chat)))
 | 
					    // console.log('----------> chat: ', JSON.parse(JSON.stringify(chat)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    console
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (chat.isGroup) {
 | 
					    if (chat.isGroup) {
 | 
				
			||||||
      // let msgGroupContact;
 | 
					      // let msgGroupContact;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,9 +1,14 @@
 | 
				
			||||||
import React, { useContext } from "react";
 | 
					import React, { useContext, Suspense, lazy } from "react";
 | 
				
			||||||
import { Route as RouterRoute, Redirect } from "react-router-dom";
 | 
					import { Route as RouterRoute, Redirect } from "react-router-dom";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { AuthContext } from "../context/Auth/AuthContext";
 | 
					import { AuthContext } from "../context/Auth/AuthContext";
 | 
				
			||||||
import BackdropLoading from "../components/BackdropLoading";
 | 
					import BackdropLoading from "../components/BackdropLoading";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Exemplo de como você carregaria componentes de forma lazy
 | 
				
			||||||
 | 
					const Dashboard = lazy(() => import("../pages/Dashboard"));
 | 
				
			||||||
 | 
					const Login = lazy(() => import("../pages/Login"));
 | 
				
			||||||
 | 
					const Signup = lazy(() => import("../pages/Signup"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Route = ({ component: Component, isPrivate = false, ...rest }) => {
 | 
					const Route = ({ component: Component, isPrivate = false, ...rest }) => {
 | 
				
			||||||
  const { isAuth, loading } = useContext(AuthContext);
 | 
					  const { isAuth, loading } = useContext(AuthContext);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,7 +25,7 @@ const Route = ({ component: Component, isPrivate = false, ...rest }) => {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <>
 | 
					      <>
 | 
				
			||||||
        {loading && <BackdropLoading />}
 | 
					        {loading && <BackdropLoading />}
 | 
				
			||||||
        <Redirect to={{ pathname: "/", state: { from: rest.location } }} />;
 | 
					        <Redirect to={{ pathname: "/", state: { from: rest.location } }} />
 | 
				
			||||||
      </>
 | 
					      </>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -28,7 +33,9 @@ const Route = ({ component: Component, isPrivate = false, ...rest }) => {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
      {loading && <BackdropLoading />}
 | 
					      {loading && <BackdropLoading />}
 | 
				
			||||||
 | 
					      <Suspense fallback={<BackdropLoading />}>
 | 
				
			||||||
        <RouterRoute {...rest} component={Component} />
 | 
					        <RouterRoute {...rest} component={Component} />
 | 
				
			||||||
 | 
					      </Suspense>
 | 
				
			||||||
    </>
 | 
					    </>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,34 +1,34 @@
 | 
				
			||||||
import React from 'react'
 | 
					import React, { Suspense, lazy } from 'react';
 | 
				
			||||||
import { BrowserRouter, Switch } from 'react-router-dom'
 | 
					import { BrowserRouter, Switch } from 'react-router-dom'
 | 
				
			||||||
import { ToastContainer } from 'react-toastify'
 | 
					import { ToastContainer } from 'react-toastify'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import LoggedInLayout from '../layout'
 | 
					 | 
				
			||||||
import Dashboard from '../pages/Dashboard/'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import Report from '../pages/Report/'
 | 
					 | 
				
			||||||
import SchedulesReminder from '../pages/SchedulesReminder/'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import Tickets from '../pages/Tickets/'
 | 
					 | 
				
			||||||
import Signup from '../pages/Signup/'
 | 
					 | 
				
			||||||
import Login from '../pages/Login/'
 | 
					 | 
				
			||||||
import Connections from '../pages/Connections/'
 | 
					 | 
				
			||||||
import Campaign from '../pages/Campaign'
 | 
					 | 
				
			||||||
import Settings from '../pages/Settings/'
 | 
					 | 
				
			||||||
import Users from '../pages/Users'
 | 
					 | 
				
			||||||
import Contacts from '../pages/Contacts/'
 | 
					 | 
				
			||||||
import QuickAnswers from '../pages/QuickAnswers/'
 | 
					 | 
				
			||||||
import StatusChatEnd from '../pages/StatusChatEnd/'
 | 
					 | 
				
			||||||
import Position from '../pages/Position/'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import Queues from '../pages/Queues/'
 | 
					 | 
				
			||||||
import { AuthProvider } from '../context/Auth/AuthContext'
 | 
					import { AuthProvider } from '../context/Auth/AuthContext'
 | 
				
			||||||
import { WhatsAppsProvider } from '../context/WhatsApp/WhatsAppsContext'
 | 
					import { WhatsAppsProvider } from '../context/WhatsApp/WhatsAppsContext'
 | 
				
			||||||
 | 
					import LoggedInLayout from '../layout'
 | 
				
			||||||
import Route from './Route'
 | 
					import Route from './Route'
 | 
				
			||||||
 | 
					import BackdropLoading from "../components/BackdropLoading";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Dashboard = lazy(() => import('../pages/Dashboard/'));
 | 
				
			||||||
 | 
					const Report = lazy(() => import('../pages/Report/'));
 | 
				
			||||||
 | 
					const SchedulesReminder = lazy(() => import('../pages/SchedulesReminder/'));
 | 
				
			||||||
 | 
					const Tickets = lazy(() => import('../pages/Tickets/'));
 | 
				
			||||||
 | 
					const Signup = lazy(() => import('../pages/Signup/'));
 | 
				
			||||||
 | 
					const Login = lazy(() => import('../pages/Login/'));
 | 
				
			||||||
 | 
					const Connections = lazy(() => import('../pages/Connections/'));
 | 
				
			||||||
 | 
					const Campaign = lazy(() => import('../pages/Campaign/'));
 | 
				
			||||||
 | 
					const Settings = lazy(() => import('../pages/Settings/'));
 | 
				
			||||||
 | 
					const Users = lazy(() => import('../pages/Users/'));
 | 
				
			||||||
 | 
					const Contacts = lazy(() => import('../pages/Contacts/'));
 | 
				
			||||||
 | 
					const QuickAnswers = lazy(() => import('../pages/QuickAnswers/'));
 | 
				
			||||||
 | 
					const StatusChatEnd = lazy(() => import('../pages/StatusChatEnd/'));
 | 
				
			||||||
 | 
					const Position = lazy(() => import('../pages/Position/'));
 | 
				
			||||||
 | 
					const Queues = lazy(() => import('../pages/Queues/'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Routes = () => {
 | 
					const Routes = () => {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <BrowserRouter>
 | 
					    <BrowserRouter>
 | 
				
			||||||
      <AuthProvider>
 | 
					      <AuthProvider>
 | 
				
			||||||
 | 
					        <Suspense fallback={<BackdropLoading />}>
 | 
				
			||||||
          <Switch>
 | 
					          <Switch>
 | 
				
			||||||
            <Route exact path="/login" component={Login} />
 | 
					            <Route exact path="/login" component={Login} />
 | 
				
			||||||
            <Route exact path="/signup" component={Signup} />
 | 
					            <Route exact path="/signup" component={Signup} />
 | 
				
			||||||
| 
						 | 
					@ -41,25 +41,20 @@ const Routes = () => {
 | 
				
			||||||
                  component={Tickets}
 | 
					                  component={Tickets}
 | 
				
			||||||
                  isPrivate
 | 
					                  isPrivate
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
 | 
					 | 
				
			||||||
                <Route
 | 
					                <Route
 | 
				
			||||||
                  exact
 | 
					                  exact
 | 
				
			||||||
                  path="/connections"
 | 
					                  path="/connections"
 | 
				
			||||||
                  component={Connections}
 | 
					                  component={Connections}
 | 
				
			||||||
                  isPrivate
 | 
					                  isPrivate
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
 | 
					 | 
				
			||||||
                <Route exact path="/report" component={Report} isPrivate />
 | 
					                <Route exact path="/report" component={Report} isPrivate />
 | 
				
			||||||
 | 
					 | 
				
			||||||
                <Route exact path="/contacts" component={Contacts} isPrivate />
 | 
					                <Route exact path="/contacts" component={Contacts} isPrivate />
 | 
				
			||||||
 | 
					 | 
				
			||||||
                <Route
 | 
					                <Route
 | 
				
			||||||
                  exact
 | 
					                  exact
 | 
				
			||||||
                  path="/schedulesReminder"
 | 
					                  path="/schedulesReminder"
 | 
				
			||||||
                  component={SchedulesReminder}
 | 
					                  component={SchedulesReminder}
 | 
				
			||||||
                  isPrivate
 | 
					                  isPrivate
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
 | 
					 | 
				
			||||||
                <Route exact path="/users" component={Users} isPrivate />
 | 
					                <Route exact path="/users" component={Users} isPrivate />
 | 
				
			||||||
                <Route
 | 
					                <Route
 | 
				
			||||||
                  exact
 | 
					                  exact
 | 
				
			||||||
| 
						 | 
					@ -73,22 +68,18 @@ const Routes = () => {
 | 
				
			||||||
                  component={StatusChatEnd}
 | 
					                  component={StatusChatEnd}
 | 
				
			||||||
                  isPrivate
 | 
					                  isPrivate
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
              <Route
 | 
					                <Route exact path="/position" component={Position} isPrivate />
 | 
				
			||||||
                exact
 | 
					 | 
				
			||||||
                path="/position"
 | 
					 | 
				
			||||||
                component={Position}
 | 
					 | 
				
			||||||
                isPrivate
 | 
					 | 
				
			||||||
              />
 | 
					 | 
				
			||||||
                <Route exact path="/Settings" component={Settings} isPrivate />
 | 
					                <Route exact path="/Settings" component={Settings} isPrivate />
 | 
				
			||||||
                <Route exact path="/Queues" component={Queues} isPrivate />
 | 
					                <Route exact path="/Queues" component={Queues} isPrivate />
 | 
				
			||||||
                <Route exact path="/campaign" component={Campaign} isPrivate />
 | 
					                <Route exact path="/campaign" component={Campaign} isPrivate />
 | 
				
			||||||
              </LoggedInLayout>
 | 
					              </LoggedInLayout>
 | 
				
			||||||
            </WhatsAppsProvider>
 | 
					            </WhatsAppsProvider>
 | 
				
			||||||
          </Switch>
 | 
					          </Switch>
 | 
				
			||||||
 | 
					        </Suspense>
 | 
				
			||||||
        <ToastContainer autoClose={3000} />
 | 
					        <ToastContainer autoClose={3000} />
 | 
				
			||||||
      </AuthProvider>
 | 
					      </AuthProvider>
 | 
				
			||||||
    </BrowserRouter>
 | 
					    </BrowserRouter>
 | 
				
			||||||
  )
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Routes
 | 
					export default Routes
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  "name": "whaticket",
 | 
					  "name": "projeto-hit",
 | 
				
			||||||
  "lockfileVersion": 2,
 | 
					  "lockfileVersion": 2,
 | 
				
			||||||
  "requires": true,
 | 
					  "requires": true,
 | 
				
			||||||
  "packages": {
 | 
					  "packages": {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue