Paweł Malicki 1 year ago
parent
commit
d03d6d60ef

+ 4 - 3
backend/Dockerfile

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

+ 0 - 16
backend/middleware/auth.js

@@ -1,16 +0,0 @@
-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;

+ 8 - 0
backend/models/Client.js

@@ -0,0 +1,8 @@
+const mongoose = require('mongoose');
+
+const clientSchema = new mongoose.Schema({
+  name: { type: String, required: true },
+  email: { type: String, required: true },
+});
+
+module.exports = mongoose.model('Client', clientSchema);

+ 5 - 5
backend/models/Invoice.js

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

+ 0 - 7
backend/models/User.js

@@ -1,7 +0,0 @@
-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);

+ 4 - 8
backend/package.json

@@ -1,17 +1,13 @@
 {
-  "name": "pmdi-backend",
+  "name": "invoice-notification-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"
+    "express": "^4.17.1",
+    "mongoose": "^5.11.15",
+    "cors": "^2.8.5"
   }
 }

+ 0 - 41
backend/routes/auth.js

@@ -1,41 +0,0 @@
-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;

+ 26 - 0
backend/routes/clients.js

@@ -0,0 +1,26 @@
+const express = require('express');
+const router = express.Router();
+const Client = require('../models/Client');
+
+// Get all clients
+router.get('/', async (req, res) => {
+  try {
+    const clients = await Client.find();
+    res.json(clients);
+  } catch (err) {
+    res.status(500).json({ message: err.message });
+  }
+});
+
+// Create a new client
+router.post('/', async (req, res) => {
+  const client = new Client(req.body);
+  try {
+    const savedClient = await client.save();
+    res.status(201).json(savedClient);
+  } catch (err) {
+    res.status(400).json({ message: err.message });
+  }
+});
+
+module.exports = router;

+ 14 - 12
backend/routes/invoices.js

@@ -1,23 +1,25 @@
 const express = require('express');
-const Invoice = require('../models/Invoice');
-const auth = require('../middleware/auth');
 const router = express.Router();
+const Invoice = require('../models/Invoice');
 
-router.get('/', auth, async (req, res) => {
+// Get all invoices
+router.get('/', 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' });
+    const invoices = await Invoice.find();
+    res.json(invoices);
+  } catch (err) {
+    res.status(500).json({ message: err.message });
   }
 });
 
-router.post('/:id/pay', auth, async (req, res) => {
+// Create a new invoice
+router.post('/', async (req, res) => {
+  const invoice = new Invoice(req.body);
   try {
-    await Invoice.findByIdAndUpdate(req.params.id, { paid: true });
-    res.sendStatus(200);
-  } catch (error) {
-    res.status(500).json({ error: 'Failed to update invoice' });
+    const savedInvoice = await invoice.save();
+    res.status(201).json(savedInvoice);
+  } catch (err) {
+    res.status(400).json({ message: err.message });
   }
 });
 

+ 16 - 17
backend/server.js

@@ -1,26 +1,25 @@
 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 cors = require('cors');
+const invoicesRouter = require('./routes/invoices');
+const clientsRouter = require('./routes/clients');
 
 const app = express();
+const PORT = process.env.PORT || 5000;
+
+// Middleware
+app.use(cors());
 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));
+// MongoDB connection
+mongoose.connect('mongodb://mongo:27017/invoice-notification', { useNewUrlParser: true, useUnifiedTopology: true })
+  .then(() => console.log('MongoDB connected'))
+  .catch(err => console.error('MongoDB connection error:', err));
 
-app.use('/auth', authRoutes);
-app.use('/invoices', invoiceRoutes);
+// Routes
+app.use('/api/invoices', invoicesRouter);
+app.use('/api/clients', clientsRouter);
 
-app.use((err, req, res, next) => {
-  console.error(err.stack);
-  res.status(500).json({ error: 'Something went wrong!' });
+app.listen(PORT, () => {
+  console.log(`Server is running on port ${PORT}`);
 });
-
-const PORT = process.env.PORT || 3000;
-app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

+ 21 - 13
docker-compose.yml

@@ -1,14 +1,21 @@
-version: '3.8'
+version: '3'
 services:
+  mongo:
+    image: mongo
+    restart: always
+    volumes:
+      - mongo-data:/data/db
+    networks:
+      - invoice-network
+
   backend:
     build:
       context: ./backend
       dockerfile: Dockerfile
     ports:
-      - "3000:3000"
-    environment:
-      MONGO_URI: mongodb://mongo:27017/invoice-db
-      JWT_SECRET: "your_jwt_secret"
+      - "5000:5000"
+    networks:
+      - invoice-network
     depends_on:
       - mongo
 
@@ -17,14 +24,15 @@ services:
       context: ./frontend
       dockerfile: Dockerfile
     ports:
-      - "8081:8081"
-
-  mongo:
-    image: mongo
-    ports:
-      - "27017:27017"
-    volumes:
-      - mongo-data:/data/db
+      - "3000:3000"
+    networks:
+      - invoice-network
+    depends_on:
+      - backend
 
 volumes:
   mongo-data:
+
+networks:
+  invoice-network:
+

+ 0 - 6
frontend/App.js

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

+ 4 - 9
frontend/Dockerfile

@@ -1,19 +1,14 @@
-# Dockerfile for frontend
+# Dockerfile for the frontend
 
 FROM node:16
 
 WORKDIR /app
 
-# Copy package.json file first to install dependencies
-COPY package.json ./
+COPY package.json package-lock.json ./
 RUN npm install
 
-# Install Expo CLI and ngrok globally
-RUN npm install -g expo-cli @expo/ngrok
-
-# Copy the rest of the application files
 COPY . .
 
-EXPOSE 8081
+EXPOSE 3000
 
-CMD ["expo-cli", "start", "--tunnel"]
+CMD ["npm", "start"]

+ 29 - 10
frontend/package.json

@@ -1,16 +1,35 @@
 {
-  "name": "invoice-notification-frontend",
+  "name": "invoice-notification-web",
   "version": "1.0.0",
-  "description": "Frontend for the invoice notification service",
-  "main": "App.js",
+  "private": true,
+  "dependencies": {
+    "axios": "^0.24.0",
+    "react": "^17.0.2",
+    "react-dom": "^17.0.2",
+    "react-router-dom": "^5.3.0"
+  },
   "scripts": {
-    "start": "npx expo start"
+    "start": "react-scripts start",
+    "build": "react-scripts build",
+    "test": "react-scripts test",
+    "eject": "react-scripts eject"
   },
-  "dependencies": {
-    "@react-native-async-storage/async-storage": "1.17.11",
-    "expo": "^48.0.0",
-    "react": "18.2.0",
-    "react-native": "0.71.14",
-    "react-native-checkbox": "^2.0.0"
+  "eslintConfig": {
+    "extends": [
+      "react-app",
+      "react-app/jest"
+    ]
+  },
+  "browserslist": {
+    "production": [
+      ">0.2%",
+      "not dead",
+      "not op_mini all"
+    ],
+    "development": [
+      "last 1 chrome version",
+      "last 1 firefox version",
+      "last 1 safari version"
+    ]
   }
 }

+ 0 - 32
frontend/screens/InvoiceScreen.js

@@ -1,32 +0,0 @@
-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;

+ 22 - 0
frontend/src/App.js

@@ -0,0 +1,22 @@
+import React from 'react';
+import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
+import Home from './pages/Home';
+import Invoices from './pages/Invoices';
+import Clients from './pages/Clients';
+
+function App() {
+  return (
+    <Router>
+      <div>
+        <h1>Invoice Notification App</h1>
+        <Switch>
+          <Route path="/" exact component={Home} />
+          <Route path="/invoices" component={Invoices} />
+          <Route path="/clients" component={Clients} />
+        </Switch>
+      </div>
+    </Router>
+  );
+}
+
+export default App;

+ 13 - 0
frontend/src/api.js

@@ -0,0 +1,13 @@
+import axios from 'axios';
+
+const API_URL = 'http://localhost:5000/api'; // Update this with your backend URL
+
+export const getInvoices = async () => {
+  const response = await axios.get(`${API_URL}/invoices`);
+  return response.data;
+};
+
+export const getClients = async () => {
+  const response = await axios.get(`${API_URL}/clients`);
+  return response.data;
+};

+ 30 - 0
frontend/src/components/ClientList.js

@@ -0,0 +1,30 @@
+import React, { useEffect, useState } from 'react';
+import { getClients } from '../api';
+
+const ClientList = () => {
+  const [clients, setClients] = useState([]);
+
+  useEffect(() => {
+    const fetchClients = async () => {
+      const data = await getClients();
+      setClients(data);
+    };
+
+    fetchClients();
+  }, []);
+
+  return (
+    <div>
+      <h3>Client List</h3>
+      <ul>
+        {clients.map(client => (
+          <li key={client._id}>
+            {client.name} - Email: {client.email}
+          </li>
+        ))}
+      </ul>
+    </div>
+  );
+};
+
+export default ClientList;

+ 30 - 0
frontend/src/components/InvoiceList.js

@@ -0,0 +1,30 @@
+import React, { useEffect, useState } from 'react';
+import { getInvoices } from '../api';
+
+const InvoiceList = () => {
+  const [invoices, setInvoices] = useState([]);
+
+  useEffect(() => {
+    const fetchInvoices = async () => {
+      const data = await getInvoices();
+      setInvoices(data);
+    };
+
+    fetchInvoices();
+  }, []);
+
+  return (
+    <div>
+      <h3>Invoice List</h3>
+      <ul>
+        {invoices.map(invoice => (
+          <li key={invoice._id}>
+            {invoice.client} - Due: {invoice.dueDate} - Paid: {invoice.paid ? 'Yes' : 'No'}
+          </li>
+        ))}
+      </ul>
+    </div>
+  );
+};
+
+export default InvoiceList;

+ 11 - 0
frontend/src/index.js

@@ -0,0 +1,11 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from './App';
+import './index.css';
+
+const root = ReactDOM.createRoot(document.getElementById('root'));
+root.render(
+  <React.StrictMode>
+    <App />
+  </React.StrictMode>
+);

+ 13 - 0
frontend/src/pages/Clients.js

@@ -0,0 +1,13 @@
+import React from 'react';
+import ClientList from '../components/ClientList';
+
+const Clients = () => {
+  return (
+    <div>
+      <h2>Clients</h2>
+      <ClientList />
+    </div>
+  );
+};
+
+export default Clients;

+ 12 - 0
frontend/src/pages/Home.js

@@ -0,0 +1,12 @@
+import React from 'react';
+
+const Home = () => {
+  return (
+    <div>
+      <h2>Welcome to the Invoice Notification App</h2>
+      <p>Use the navigation to view invoices and clients.</p>
+    </div>
+  );
+};
+
+export default Home;

+ 13 - 0
frontend/src/pages/Invoices.js

@@ -0,0 +1,13 @@
+import React from 'react';
+import InvoiceList from '../components/InvoiceList';
+
+const Invoices = () => {
+  return (
+    <div>
+      <h2>Invoices</h2>
+      <InvoiceList />
+    </div>
+  );
+};
+
+export default Invoices;

+ 0 - 31
frontend/utils/api.js

@@ -1,31 +0,0 @@
-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');
-  }
-};