<|start_filename|>amplify/backend/function/closeInvoice/src/index.js<|end_filename|>
const AWS = require('aws-sdk');
const db = new AWS.DynamoDB.DocumentClient({ region: 'us-west-2' });
const TABLE_PARKING = 'Parking';
const TABLE_USER = 'ParkingUser';

const prepareProperties = item => ({
  parkingID: item.parkingID,
  invoiceID: item.invoiceID,
  slotNumber: item.slotNumber,
  dateFrom: String(item.dateFrom),
  dateTo: String(item.dateTo),
  plateNumber: item.plateNumber,
  price: item.price,
  parking: {}
});

const updateInvoice = ({ userID, invoiceID, dateTo, price }) => {
  const params = {
    TableName: TABLE_USER,
    Key: {
      userID,
      invoiceID
    },
    UpdateExpression: 'set dateTo = :dateTo, price = :price',
    ExpressionAttributeValues: {
      ':dateTo': Number(dateTo),
      ':price': price
    },
    ReturnValues: 'ALL_NEW'
  };

  return db.update(params).promise();
};

const fetchParkingRate = async parkingID => {
  const params = {
    TableName: TABLE_PARKING,
    Key: {
      parkingID,
      slotNumber: 'info'
    },
    ProjectionExpression: 'rate'
  };

  const {
    Item: { rate }
  } = await db.get(params).promise();
  return rate;
};

const fetchInvoiceDateFrom = async ({ userID, invoiceID }) => {
  const params = {
    TableName: TABLE_USER,
    Key: {
      userID,
      invoiceID
    },
    ProjectionExpression: 'dateFrom'
  };

  const {
    Item: { dateFrom }
  } = await db.get(params).promise();
  return Number(dateFrom);
};

const getParkingPrice = ({ durationInHours, rate }) => (durationInHours * rate).toFixed(2);

const getParkingDurationInHours = ({ dateFrom, dateTo }) => Math.ceil((dateTo - dateFrom) / 1000 / 60 / 60);

const closeInvoice = async ({ parkingID, userID, invoiceID }) => {
  const [parkingRate, invoiceDateFrom] = await Promise.all([
    fetchParkingRate(parkingID),
    fetchInvoiceDateFrom({ userID, invoiceID })
  ]);

  const dateTo = Date.now();

  const durationInHours = getParkingDurationInHours({ dateFrom: invoiceDateFrom, dateTo });
  const price = getParkingPrice({ durationInHours, rate: parkingRate });

  const { Attributes: invoice } = await updateInvoice({ userID, invoiceID, dateTo, price });
  return prepareProperties(invoice);
};

exports.handler = async (event, context) => {
  try {
    const invoiceData = {
      ...event.arguments.input,
      userID: event.identity.sub
    };
    const result = await closeInvoice(invoiceData);
    context.done(null, result);
  } catch (err) {
    context.done(err, null);
  }
};


<|start_filename|>amplify/backend/api/parking/parameters.json<|end_filename|>
{
    "AppSyncApiName": "parking",
    "DynamoDBBillingMode": "PAY_PER_REQUEST",
    "AuthCognitoUserPoolId": {
        "Fn::GetAtt": [
            "authsmartparkingd67f4dd0",
            "Outputs.UserPoolId"
        ]
    }
}

<|start_filename|>amplify/backend/function/listParkingSlotsBatch/src/index.js<|end_filename|>
const AWS = require('aws-sdk');
const db = new AWS.DynamoDB.DocumentClient({ region: 'us-west-2' });
const DYNAMODB_TABLE = 'Parking';

const prepareProperties = item => ({
  parkingID: item.parkingID,
  device: item.device,
  slotNumber: item.slotNumber,
  slotStatus: item.slotStatus
});

async function listParkingSlots(parkingArray) {
  const filterInKeys = parkingArray.map((v, i) => `:${i}`).join(', ');
  const values = {
    ':info': 'info'
  };
  parkingArray.forEach((v, i) => (values[`:${i}`] = v.parkingID));

  const params = {
    TableName: DYNAMODB_TABLE,
    FilterExpression: `parkingID IN (${filterInKeys}) AND slotNumber <> :info`,
    ExpressionAttributeValues: values
  };

  const { Items } = await db.scan(params).promise();
  const result = parkingArray.map(({ parkingID }) =>
    Items.filter(item => item.parkingID === parkingID).map(prepareProperties)
  );

  return result;
}

exports.handler = async (event, context) => {
  try {
    const result = await listParkingSlots(event);
    context.done(null, result);
  } catch (err) {
    context.done(err, null);
  }
};


<|start_filename|>src/components/App.css<|end_filename|>
:root {
  height: 100%;
  background-color: #e8eaed;
}

body {
  height: 100%;
}

a {
  color: #1976d2;
}

canvas {
  display: block;
}

#root {
  height: 100%;
}


<|start_filename|>amplify/backend/function/updateSlot/src/index.js<|end_filename|>
const AWS = require('aws-sdk');
const db = new AWS.DynamoDB.DocumentClient({ region: 'us-west-2' });
const DYNAMODB_TABLE = 'Parking';

const prepareProperties = item => ({
  parkingID: item.parkingID,
  slotNumber: item.slotNumber,
  slotStatus: item.slotStatus,
  device: item.device
});

const getSlotNumber = async ({ parkingID, device }) => {
  const params = {
    TableName: DYNAMODB_TABLE,
    IndexName: 'parkingDeviceIndex',
    KeyConditionExpression: 'parkingID = :parkingID AND device = :device',
    ExpressionAttributeValues: {
      ':parkingID': parkingID,
      ':device': device
    },
    ProjectionExpression: 'slotNumber'
  };

  const { Items } = await db.query(params).promise();
  if (Items && Items.length) {
    const [ { slotNumber } ] = Items;
    return slotNumber;
  }
  
  return null;
}

const updateSlotStatus = ({ parkingID, slotNumber, slotStatus }) => {
  const params = {
    TableName: DYNAMODB_TABLE,
    Key: {
      parkingID,
      slotNumber
    },
    UpdateExpression: 'set slotStatus = :slotStatus',
    ExpressionAttributeValues: {
      ':slotStatus': slotStatus
    },
    ReturnValues: 'ALL_NEW'
  };

  return db.update(params).promise();
};

const updateSlot = async ({ parkingID, device, slotStatus }) => {
  const slotNumber = await getSlotNumber({ parkingID, device });
  if (slotNumber) {
    const { Attributes: slot } = await updateSlotStatus({ parkingID, slotNumber, slotStatus });
    return prepareProperties(slot);
  }

  return null;
};

exports.handler = async (event, context) => {
  try {
    const result = await updateSlot(event.arguments.input);
    context.done(null, result);
  } catch (err) {
    context.done(err, null);
  }
};


<|start_filename|>amplify/backend/function/listParking/src/index.js<|end_filename|>
const AWS = require('aws-sdk');
const db = new AWS.DynamoDB.DocumentClient({ region: 'us-west-2' });
const DYNAMODB_TABLE = 'Parking';

const getArray = obj => (Array.isArray(obj) ? obj : []);

const prepareOpeningHours = openingHours =>
  getArray(openingHours).map(item => ({
    open: item.open,
    close: item.close
  }));

const prepareProperties = item => ({
  parkingID: item.parkingID,
  address: item.address,
  features: item.features,
  location: item.location,
  openingHours: prepareOpeningHours(item.openingHours),
  rate: item.rate,
  title: item.title,
  freeSlots: 5, // TODO: get count of free slots for each parking
  slots: []
});

function fetchParkingByFilter(filter) {
  const params = {
    TableName: DYNAMODB_TABLE,
    FilterExpression: 'slotNumber = :slotNumber',
    ExpressionAttributeValues: {
      ':slotNumber': 'info'
    }
  };

  return db.scan(params).promise();
}

function fetchParkingByID(parkingID) {
  const params = {
    TableName: DYNAMODB_TABLE,
    Key: {
      parkingID,
      slotNumber: 'info'
    }
  };

  return db.get(params).promise();
}

async function listParking(filter) {
  if (filter && filter.parkingID) {
    const { Item } = await fetchParkingByID(filter.parkingID);
    return [prepareProperties(Item)];
  }

  const { Items } = await fetchParkingByFilter(filter);
  return Items.map(prepareProperties);
}

exports.handler = async (event, context) => {
  try {
    const result = await listParking(event.arguments.filter);
    context.done(null, result);
  } catch (err) {
    context.done(err, null);
  }
};


<|start_filename|>amplify/backend/backend-config.json<|end_filename|>
{
	"auth": {
		"smartparkingd67f4dd0": {
			"service": "Cognito",
			"providerPlugin": "awscloudformation"
		}
	},
	"api": {
		"parking": {
			"service": "AppSync",
			"providerPlugin": "awscloudformation",
			"output": {
				"securityType": "AMAZON_COGNITO_USER_POOLS"
			}
		}
	},
	"function": {
		"listParking": {
			"service": "Lambda",
			"providerPlugin": "awscloudformation",
			"build": true
		},
		"listParkingSlotsBatch": {
			"service": "Lambda",
			"providerPlugin": "awscloudformation",
			"build": true
		},
		"updateSlot": {
			"service": "Lambda",
			"providerPlugin": "awscloudformation",
			"build": true
		},
		"getCurrentUser": {
			"service": "Lambda",
			"providerPlugin": "awscloudformation",
			"build": true
		},
		"createInvoice": {
			"service": "Lambda",
			"providerPlugin": "awscloudformation",
			"build": true
		},
		"closeInvoice": {
			"service": "Lambda",
			"providerPlugin": "awscloudformation",
			"build": true
		},
		"listParkingBatch": {
			"service": "Lambda",
			"providerPlugin": "awscloudformation",
			"build": true
		}
	},
	"hosting": {
		"S3AndCloudFront": {
			"service": "S3AndCloudFront",
			"providerPlugin": "awscloudformation"
		}
	}
}

<|start_filename|>amplify/backend/function/createInvoice/src/index.js<|end_filename|>
const AWS = require('aws-sdk');
const uuidv1 = require('uuid/v1');
const db = new AWS.DynamoDB.DocumentClient({ region: 'us-west-2' });
const USER_TABLE = 'ParkingUser';
const PARKING_TABLE = 'Parking';

const prepareProperties = item => ({
  parkingID: item.parkingID,
  invoiceID: item.invoiceID,
  slotNumber: item.slotNumber,
  dateFrom: String(item.dateFrom),
  dateTo: String(item.dateTo),
  plateNumber: item.plateNumber,
  price: 0,
  parking: {}
});

const createInvoice = async ({ userID, invoiceID, parkingID, slotNumber, dateFrom, plateNumber }) => {
  const params = {
    TableName: USER_TABLE,
    Item: {
      userID,
      invoiceID,
      parkingID,
      slotNumber,
      dateFrom: Number(dateFrom),
      plateNumber
    }
  };

  return db.put(params).promise();
};

const getInvoice = async ({ userID, invoiceID }) => {
  const params = {
    TableName: USER_TABLE,
    Key: {
      userID,
      invoiceID
    }
  };

  const { Item } = await db.get(params).promise();
  return prepareProperties(Item);
};

const getInvoiceData = event => ({
  ...event.arguments.input,
  userID: event.identity.sub,
  invoiceID: uuidv1(),
  dateFrom: String(Date.now())
});

const getParkingSlot = async ({ parkingID, slotNumber }) => {
  const params = {
    TableName: PARKING_TABLE,
    Key: {
      parkingID,
      slotNumber
    }
  };

  const { Item } = await db.get(params).promise();
  return Item;
}

const getIsParkingSlotExists = async ({ parkingID, slotNumber }) => !! await getParkingSlot({ parkingID, slotNumber });

exports.handler = async (event, context) => {
  try {
    const isParkingSlotExists = await getIsParkingSlotExists(event.arguments.input);
    if (!isParkingSlotExists) {
      return context.done({
        statusCode: 400,
        message: 'Parking slot not found.'
      }, null);
    }

    const invoiceData = getInvoiceData(event);
    await createInvoice(invoiceData);
    const result = await getInvoice(invoiceData);
    
    context.done(null, result);
  } catch (err) {
    context.done(err, null);
  }
};


<|start_filename|>amplify/backend/function/getCurrentUser/src/index.js<|end_filename|>
const AWS = require('aws-sdk');
const db = new AWS.DynamoDB.DocumentClient({ region: 'us-west-2' });
const DYNAMODB_TABLE = 'ParkingUser';

const prepareUserProperties = item => ({
  userID: item.userID,
  vehicles: item.vehicles,
  fullName: item.fullName
});

const prepareInvoiceProperties = item => ({
  parkingID: item.parkingID,
  invoiceID: item.invoiceID,
  slotNumber: item.slotNumber,
  dateFrom: String(item.dateFrom),
  dateTo: String(item.dateTo),
  plateNumber: item.plateNumber,
  price: item.price,
  parking: {}
});

const getUserAndInvoices = userID => {
  const params = {
    TableName: DYNAMODB_TABLE,
    KeyConditionExpression: 'userID = :userID',
    ExpressionAttributeValues: { ':userID': userID }
  };

  return db.query(params).promise();
};

async function getUser({ userID, email, phone }) {
  const { Items } = await getUserAndInvoices(userID);
  const invoices = Items
    .filter(item => item.invoiceID !== 'info')
    .sort((a, b) => a.dateFrom > b.dateFrom ? -1 : a.dateFrom < b.dateFrom ? 1 : 0)
    .map(prepareInvoiceProperties);

  const userRaw = Items.find(item => item.invoiceID === 'info');
  const user = prepareUserProperties(userRaw);

  return {
    ...user,
    invoices,
    email,
    phone
  };
}

exports.handler = async (event, context) => {
  try {
    // get AWS Cognito user ID
    const {
      identity: {
        sub: userID,
        claims: { phone_number: phone, email }
      }
    } = event;

    const result = await getUser({ userID, email, phone });
    context.done(null, result);
  } catch (err) {
    context.done(err, null);
  }
};
