Paweł Malicki 1 gadu atpakaļ
vecāks
revīzija
b2be23bad2

+ 13 - 0
backend/Dockerfile

@@ -0,0 +1,13 @@
+# Dockerfile for backend
+
+FROM node:16
+
+WORKDIR /app
+
+COPY package*.json ./
+RUN npm install
+
+COPY . .
+
+EXPOSE 3000
+CMD ["node", "server.js"]

+ 16 - 0
backend/middleware/auth.js

@@ -0,0 +1,16 @@
+const jwt = require('jsonwebtoken');
+
+const auth = (req, res, next) => {
+  const token = req.header('Authorization');
+  if (!token) return res.status(401).json({ message: 'No token, authorization denied' });
+
+  try {
+    const decoded = jwt.verify(token, process.env.JWT_SECRET);
+    req.user = decoded;
+    next();
+  } catch (error) {
+    res.status(401).json({ message: 'Token is not valid' });
+  }
+};
+
+module.exports = auth;

+ 9 - 0
backend/models/Invoice.js

@@ -0,0 +1,9 @@
+const mongoose = require('mongoose');
+const invoiceSchema = new mongoose.Schema({
+  invoiceDate: Date,
+  clientId: mongoose.Schema.Types.ObjectId,
+  invoiceNumber: String,
+  dueDate: Date,
+  paid: Boolean,
+});
+module.exports = mongoose.model('Invoice', invoiceSchema);

+ 7 - 0
backend/models/User.js

@@ -0,0 +1,7 @@
+const mongoose = require('mongoose');
+const userSchema = new mongoose.Schema({
+  username: { type: String, required: true, unique: true },
+  email: { type: String, required: true, unique: true },
+  password: { type: String, required: true },
+});
+module.exports = mongoose.model('User', userSchema);

+ 17 - 0
backend/package.json

@@ -0,0 +1,17 @@
+{
+  "name": "pmdi-backend",
+  "version": "1.0.0",
+  "description": "Backend for the invoice notification service",
+  "main": "server.js",
+  "scripts": {
+    "start": "node server.js"
+  },
+  "dependencies": {
+    "bcryptjs": "^2.4.3",
+    "dotenv": "^16.0.3",
+    "express": "^4.18.2",
+    "express-validator": "^6.14.0",
+    "jsonwebtoken": "^9.0.0",
+    "mongoose": "^7.2.3"
+  }
+}

+ 41 - 0
backend/routes/auth.js

@@ -0,0 +1,41 @@
+const express = require('express');
+const bcrypt = require('bcryptjs');
+const jwt = require('jsonwebtoken');
+const User = require('../models/User');
+const { body, validationResult } = require('express-validator');
+const router = express.Router();
+
+router.post('/register', [
+  body('username').notEmpty(),
+  body('email').isEmail(),
+  body('password').isLength({ min: 6 }),
+], async (req, res) => {
+  const errors = validationResult(req);
+  if (!errors.isEmpty()) return res.status(400).json({ errors: errors.array() });
+
+  const { username, email, password } = req.body;
+  try {
+    const hashedPassword = await bcrypt.hash(password, 10);
+    const newUser = new User({ username, email, password: hashedPassword });
+    await newUser.save();
+    res.status(201).json({ message: 'User registered successfully' });
+  } catch (error) {
+    res.status(500).json({ error: 'Server error' });
+  }
+});
+
+router.post('/login', async (req, res) => {
+  const { email, password } = req.body;
+  try {
+    const user = await User.findOne({ email });
+    if (!user || !(await bcrypt.compare(password, user.password))) {
+      return res.status(400).json({ error: 'Invalid credentials' });
+    }
+    const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '1h' });
+    res.json({ token });
+  } catch (error) {
+    res.status(500).json({ error: 'Server error' });
+  }
+});
+
+module.exports = router;

+ 24 - 0
backend/routes/invoices.js

@@ -0,0 +1,24 @@
+const express = require('express');
+const Invoice = require('../models/Invoice');
+const auth = require('../middleware/auth');
+const router = express.Router();
+
+router.get('/', auth, async (req, res) => {
+  try {
+    const unpaidInvoices = await Invoice.find({ paid: false });
+    res.json(unpaidInvoices);
+  } catch (error) {
+    res.status(500).json({ error: 'Failed to retrieve invoices' });
+  }
+});
+
+router.post('/:id/pay', auth, async (req, res) => {
+  try {
+    await Invoice.findByIdAndUpdate(req.params.id, { paid: true });
+    res.sendStatus(200);
+  } catch (error) {
+    res.status(500).json({ error: 'Failed to update invoice' });
+  }
+});
+
+module.exports = router;

+ 26 - 0
backend/server.js

@@ -0,0 +1,26 @@
+const express = require('express');
+const mongoose = require('mongoose');
+const dotenv = require('dotenv');
+const authRoutes = require('./routes/auth');
+const invoiceRoutes = require('./routes/invoices');
+
+dotenv.config();
+
+const app = express();
+app.use(express.json());
+
+mongoose.connect(process.env.MONGO_URI, {
+  useNewUrlParser: true,
+  useUnifiedTopology: true
+}).then(() => console.log('Connected to MongoDB')).catch(err => console.error(err));
+
+app.use('/auth', authRoutes);
+app.use('/invoices', invoiceRoutes);
+
+app.use((err, req, res, next) => {
+  console.error(err.stack);
+  res.status(500).json({ error: 'Something went wrong!' });
+});
+
+const PORT = process.env.PORT || 3000;
+app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

+ 30 - 0
docker-compose.yml

@@ -0,0 +1,30 @@
+version: '3.8'
+services:
+  backend:
+    build:
+      context: ./backend
+      dockerfile: Dockerfile
+    ports:
+      - "3000:3000"
+    environment:
+      MONGO_URI: mongodb://mongo:27017/invoice-db
+      JWT_SECRET: "your_jwt_secret"
+    depends_on:
+      - mongo
+
+  frontend:
+    build:
+      context: ./frontend
+      dockerfile: Dockerfile
+    ports:
+      - "8081:8081"
+
+  mongo:
+    image: mongo
+    ports:
+      - "27017:27017"
+    volumes:
+      - mongo-data:/data/db
+
+volumes:
+  mongo-data:

+ 6 - 0
frontend/App.js

@@ -0,0 +1,6 @@
+import React from 'react';
+import InvoiceScreen from './screens/InvoiceScreen';
+
+const App = () => <InvoiceScreen />;
+
+export default App;

+ 11 - 0
frontend/Dockerfile

@@ -0,0 +1,11 @@
+FROM node:16
+
+WORKDIR /app
+
+COPY package*.json ./
+RUN npm install
+
+COPY . .
+
+EXPOSE 8081
+CMD ["npm", "start"]

+ 16 - 0
frontend/package.json

@@ -0,0 +1,16 @@
+{
+  "name": "invoice-notification-frontend",
+  "version": "1.0.0",
+  "description": "Frontend for the invoice notification service",
+  "main": "App.js",
+  "scripts": {
+    "start": "npx expo start"
+  },
+  "dependencies": {
+    "@react-native-async-storage/async-storage": "^1.15.9",
+    "expo": "^48.0.0",
+    "react": "18.2.0",
+    "react-native": "0.71.0",
+    "react-native-checkbox": "^2.0.0"
+  }
+}

+ 32 - 0
frontend/screens/InvoiceScreen.js

@@ -0,0 +1,32 @@
+import React, { useEffect, useState } from 'react';
+import { View, Text, FlatList, CheckBox, Alert } from 'react-native';
+import { fetchInvoices, markAsPaid } from '../utils/api';
+
+const InvoiceScreen = () => {
+  const [invoices, setInvoices] = useState([]);
+
+  useEffect(() => {
+    fetchInvoices(setInvoices);
+  }, []);
+
+  return (
+    <View>
+      <FlatList
+        data={invoices}
+        keyExtractor={(item) => item._id}
+        renderItem={({ item }) => (
+          <View>
+            <Text>{`Invoice Number: ${item.invoiceNumber}`}</Text>
+            <Text>{`Due Date: ${item.dueDate}`}</Text>
+            <CheckBox
+              value={item.paid}
+              onValueChange={() => markAsPaid(item._id, setInvoices)}
+            />
+          </View>
+        )}
+      />
+    </View>
+  );
+};
+
+export default InvoiceScreen;

+ 31 - 0
frontend/utils/api.js

@@ -0,0 +1,31 @@
+import AsyncStorage from '@react-native-async-storage/async-storage';
+
+export const fetchInvoices = async (setInvoices) => {
+  try {
+    const token = await AsyncStorage.getItem('token');
+    const response = await fetch('http://localhost:3000/invoices', {
+      headers: { 'Authorization': token },
+    });
+    if (response.ok) {
+      const data = await response.json();
+      setInvoices(data);
+    } else {
+      throw new Error('Failed to fetch invoices');
+    }
+  } catch (error) {
+    Alert.alert('Error', 'Failed to fetch invoices');
+  }
+};
+
+export const markAsPaid = async (invoiceId, setInvoices) => {
+  try {
+    const token = await AsyncStorage.getItem('token');
+    await fetch(`http://localhost:3000/invoice/${invoiceId}/pay`, {
+      method: 'POST',
+      headers: { 'Authorization': token },
+    });
+    fetchInvoices(setInvoices);
+  } catch (error) {
+    Alert.alert('Error', 'Failed to mark invoice as paid');
+  }
+};