mirror of
https://github.com/R0m1k3/Regisflow.git
synced 2026-10-11 17:29:48 +02:00
Restored to '3891ae828b859a29b86a84154ffef43740610335'
Replit-Restored-To: 3891ae828b
This commit is contained in:
1 parent
2f847dd333
commit
0821b70b7d
8 files changed
+212
-694
No files matched your search
Binary file not shown.
|
Before Width: | Height: | Size: 115 KiB |
@@ -1,450 +0,0 @@
|
||||
--
|
||||
-- PostgreSQL database dump
|
||||
--
|
||||
|
||||
-- Dumped from database version 16.9
|
||||
-- Dumped by pg_dump version 16.5
|
||||
|
||||
SET statement_timeout = 0;
|
||||
SET lock_timeout = 0;
|
||||
SET idle_in_transaction_session_timeout = 0;
|
||||
SET client_encoding = 'UTF8';
|
||||
SET standard_conforming_strings = on;
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
SET check_function_bodies = false;
|
||||
SET xmloption = content;
|
||||
SET client_min_messages = warning;
|
||||
SET row_security = off;
|
||||
|
||||
ALTER TABLE IF EXISTS ONLY public.users DROP CONSTRAINT IF EXISTS users_store_id_stores_id_fk;
|
||||
ALTER TABLE IF EXISTS ONLY public.sales DROP CONSTRAINT IF EXISTS sales_user_id_users_id_fk;
|
||||
ALTER TABLE IF EXISTS ONLY public.sales DROP CONSTRAINT IF EXISTS sales_store_id_stores_id_fk;
|
||||
ALTER TABLE IF EXISTS ONLY public.sale_products DROP CONSTRAINT IF EXISTS sale_products_sale_id_sales_id_fk;
|
||||
DROP INDEX IF EXISTS public."IDX_session_expire";
|
||||
ALTER TABLE IF EXISTS ONLY public.users DROP CONSTRAINT IF EXISTS users_username_unique;
|
||||
ALTER TABLE IF EXISTS ONLY public.users DROP CONSTRAINT IF EXISTS users_pkey;
|
||||
ALTER TABLE IF EXISTS ONLY public.users DROP CONSTRAINT IF EXISTS users_email_unique;
|
||||
ALTER TABLE IF EXISTS ONLY public.stores DROP CONSTRAINT IF EXISTS stores_pkey;
|
||||
ALTER TABLE IF EXISTS ONLY public.sessions DROP CONSTRAINT IF EXISTS sessions_pkey;
|
||||
ALTER TABLE IF EXISTS ONLY public.sales DROP CONSTRAINT IF EXISTS sales_pkey;
|
||||
ALTER TABLE IF EXISTS ONLY public.sale_products DROP CONSTRAINT IF EXISTS sale_products_pkey;
|
||||
ALTER TABLE IF EXISTS public.users ALTER COLUMN id DROP DEFAULT;
|
||||
ALTER TABLE IF EXISTS public.stores ALTER COLUMN id DROP DEFAULT;
|
||||
ALTER TABLE IF EXISTS public.sales ALTER COLUMN id DROP DEFAULT;
|
||||
ALTER TABLE IF EXISTS public.sale_products ALTER COLUMN id DROP DEFAULT;
|
||||
DROP SEQUENCE IF EXISTS public.users_id_seq;
|
||||
DROP TABLE IF EXISTS public.users;
|
||||
DROP SEQUENCE IF EXISTS public.stores_id_seq;
|
||||
DROP TABLE IF EXISTS public.stores;
|
||||
DROP TABLE IF EXISTS public.sessions;
|
||||
DROP SEQUENCE IF EXISTS public.sales_id_seq;
|
||||
DROP TABLE IF EXISTS public.sales;
|
||||
DROP SEQUENCE IF EXISTS public.sale_products_id_seq;
|
||||
DROP TABLE IF EXISTS public.sale_products;
|
||||
SET default_tablespace = '';
|
||||
|
||||
SET default_table_access_method = heap;
|
||||
|
||||
--
|
||||
-- Name: sale_products; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.sale_products (
|
||||
id integer NOT NULL,
|
||||
sale_id integer NOT NULL,
|
||||
type_article character varying(255) NOT NULL,
|
||||
categorie character varying(2) NOT NULL,
|
||||
quantite integer NOT NULL,
|
||||
gencode character varying(13) NOT NULL,
|
||||
created_at timestamp without time zone DEFAULT now()
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: sale_products_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.sale_products_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- Name: sale_products_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.sale_products_id_seq OWNED BY public.sale_products.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: sales; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.sales (
|
||||
id integer NOT NULL,
|
||||
store_id integer NOT NULL,
|
||||
user_id integer NOT NULL,
|
||||
"timestamp" timestamp without time zone DEFAULT now(),
|
||||
vendeur character varying(255) NOT NULL,
|
||||
nom character varying(255) NOT NULL,
|
||||
prenom character varying(255) NOT NULL,
|
||||
date_naissance character varying(10) NOT NULL,
|
||||
lieu_naissance character varying(255),
|
||||
type_identite character varying(50) NOT NULL,
|
||||
numero_identite character varying(100) NOT NULL,
|
||||
autorite_delivrance character varying(255),
|
||||
date_delivrance character varying(10) NOT NULL,
|
||||
photo_recto text,
|
||||
photo_verso text,
|
||||
mode_paiement character varying(50) DEFAULT 'Espèce'::character varying NOT NULL,
|
||||
photo_ticket text
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: sales_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.sales_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- Name: sales_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.sales_id_seq OWNED BY public.sales.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: sessions; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.sessions (
|
||||
sid character varying NOT NULL,
|
||||
sess jsonb NOT NULL,
|
||||
expire timestamp without time zone NOT NULL
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: stores; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.stores (
|
||||
id integer NOT NULL,
|
||||
name character varying(255) NOT NULL,
|
||||
address text,
|
||||
phone character varying(20),
|
||||
email character varying(255),
|
||||
is_active boolean DEFAULT true,
|
||||
created_at timestamp without time zone DEFAULT now(),
|
||||
updated_at timestamp without time zone DEFAULT now()
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: stores_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.stores_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- Name: stores_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.stores_id_seq OWNED BY public.stores.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: users; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.users (
|
||||
id integer NOT NULL,
|
||||
username character varying(50) NOT NULL,
|
||||
password character varying(255) NOT NULL,
|
||||
email character varying(255),
|
||||
first_name character varying(100),
|
||||
last_name character varying(100),
|
||||
role character varying(20) DEFAULT 'employee'::character varying NOT NULL,
|
||||
store_id integer,
|
||||
is_active boolean DEFAULT true,
|
||||
created_at timestamp without time zone DEFAULT now(),
|
||||
updated_at timestamp without time zone DEFAULT now()
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: users_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.users_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- Name: users_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.users_id_seq OWNED BY public.users.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: sale_products id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.sale_products ALTER COLUMN id SET DEFAULT nextval('public.sale_products_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: sales id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.sales ALTER COLUMN id SET DEFAULT nextval('public.sales_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: stores id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.stores ALTER COLUMN id SET DEFAULT nextval('public.stores_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: users id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.users ALTER COLUMN id SET DEFAULT nextval('public.users_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: sale_products; Type: TABLE DATA; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
COPY public.sale_products (id, sale_id, type_article, categorie, quantite, gencode, created_at) FROM stdin;
|
||||
1 1 Pétard aérien F3 1 3660092323745 2025-07-09 19:05:20.904549
|
||||
2 2 Pétard aérien F3 1 3660092323745 2025-07-09 19:07:15.650704
|
||||
3 4 Batterie F3 1 3660092323745 2025-07-09 19:11:36.505833
|
||||
4 5 Fusée F2 10 3660092323745 2025-07-09 19:16:46.797165
|
||||
5 6 Chandelle romaine F2 1 3660092323745 2025-07-10 00:08:19.097391
|
||||
6 7 Batterie F3 1 3660092323745 2025-07-11 15:36:32.19478
|
||||
7 8 Batterie Test F2 1 1234567890123 2025-07-11 18:27:55.231199
|
||||
8 9 Chandelle F2 1 1234567890123 2025-07-11 18:38:21.913752
|
||||
9 10 Chandelle F2 1 1234567890124 2025-07-11 18:38:45.251595
|
||||
10 11 Chandelle F2 1 1234567890125 2025-07-11 18:39:03.438134
|
||||
11 12 Chandelle F2 1 1234567890999 2025-07-11 18:48:31.159612
|
||||
12 13 Chandelle F2 1 1234567890999 2025-07-11 19:02:01.423938
|
||||
13 14 Test F2 1 1234567890123 2025-07-13 11:24:50.830933
|
||||
14 16 Pétard à mèche F3 1 3560238902912 2025-07-19 10:35:03.315083
|
||||
15 16 Pétard à composition flash F3 1 3560238902912 2025-07-19 10:35:03.315083
|
||||
16 17 Batterie F3 1 3560238902912 2025-07-19 10:36:48.91106
|
||||
17 17 Combinaison nécessitant un support externe F3 1 3560238902912 2025-07-19 10:36:48.91106
|
||||
\.
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: sales; Type: TABLE DATA; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
COPY public.sales (id, store_id, user_id, "timestamp", vendeur, nom, prenom, date_naissance, lieu_naissance, type_identite, numero_identite, autorite_delivrance, date_delivrance, photo_recto, photo_verso, mode_paiement, photo_ticket) FROM stdin;
|
||||
1 2 1 2025-07-09 19:05:20.904549 Moi SCHAL Michaël 2025-07-02 Bar le duc CNI qsdfqsdgc23445 Frouard 2025-07-01 \N \N Espèce \N
|
||||
2 2 1 2025-07-09 19:07:15.650704 Moi SCHAL Michaël 2025-07-02 Bar le duc CNI qsdfqsdgc23445 Frouard 2025-07-01 \N \N Espèce \N
|
||||
4 1 1 2025-07-09 19:11:36.505833 Moi SCHAL Michaël 2025-07-18 Bar le duc CNI qsdfqsdgc23445 Frouard 2025-07-01 \N \N Espèce \N
|
||||
5 2 2 2025-07-09 19:16:46.797165 Moi ASSOCIATION GRAND AIR Anne-Laure 2025-06-03 Bar le duc Passeport qsdfqsdgc23445 Frouard 2025-07-01 \N \N Espèce \N
|
||||
6 1 1 2025-07-10 00:08:19.097391 Moi SCHAL Anne-Laure 2018-02-10 Bar le duc Passeport qsdfqsdgc23445 Frouard 2025-07-01 \N \N Espèce \N
|
||||
7 2 1 2025-07-11 15:36:32.19478 Moi SCHAL Michaël 1998-02-05 Bar le duc CNI qsdfqsdgc23445 Frouard 2025-07-11 \N \N Espèce \N
|
||||
8 2 1 2025-07-11 18:27:55.231199 Test Vendeur DUPONT Jean 01/01/1980 Paris Carte d'identité ABC123456 Préfecture de Paris 01/01/2020 data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== Carte bancaire data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
|
||||
9 1 1 2025-07-11 18:38:21.913752 Test Vendeur TEST Photo 1990-01-01 Test Passeport 123456789 Test 2025-01-01 \N \N Espèce \N
|
||||
10 1 1 2025-07-11 18:38:45.251595 Test Vendeur 2 TEST2 Photo2 1990-01-01 Test Passeport 123456789 Test 2025-01-01 \N \N Espèce \N
|
||||
11 1 1 2025-07-11 18:39:03.438134 Test Debug DEBUG Test 1990-01-01 Test Passeport 123456789 Test 2025-01-01 \N \N Espèce data:image/jpeg;base64,TEST_TICKET_DATA
|
||||
12 1 1 2025-07-11 18:48:31.159612 Test Final FINAL Test 1990-01-01 Test Passeport 123456789 Test 2025-01-01 \N \N Espèce data:image/jpeg;base64,TICKET_TEST_FINAL
|
||||
13 1 1 2025-07-11 19:02:01.423938 Production Test PROD Test 1990-01-01 Test Passeport 123456789 Test 2025-01-01 \N \N Espèce data:image/jpeg;base64,PRODUCTION_TEST_TICKET
|
||||
14 1 1 2025-07-13 11:24:50.830933 TEST PRODUCTION PRODUCTION Ready 1990-01-01 Test CNI 123456789 Test 2025-01-01 \N \N Espèce data:image/jpeg;base64,PRODUCTION_READY_TEST
|
||||
16 2 1 2025-07-19 10:35:03.315083 Michael SCHAL SCHAL Michael 2025-07-04 Bar le duc CNI qsdfqsdgc23445 Frouard 2025-07-02 \N \N Espèce \N
|
||||
17 2 1 2025-07-19 10:36:48.91106 Michael SCHAL SCHAL Michael 2025-07-10 Bar le duc CNI qsdfqsdgc23445 Frouard 2025-07-23 data:image/jpeg;base64,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 truncated
|
||||
\.
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: sessions; Type: TABLE DATA; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
COPY public.sessions (sid, sess, expire) FROM stdin;
|
||||
JCyX3kHULmnhgSEZU0FaKOzcDWM1b-Mf {"cookie": {"path": "/", "secure": false, "expires": "2025-07-20T10:33:27.610Z", "httpOnly": true, "originalMaxAge": 86400000}, "userId": 1} 2025-07-20 10:33:28
|
||||
q_4oSSI08wziMN8PqUr-eoLkdu9QBCk_ {"cookie": {"path": "/", "secure": false, "expires": "2025-07-20T11:17:09.311Z", "httpOnly": true, "originalMaxAge": 86400000}, "userId": 1} 2025-07-20 11:17:10
|
||||
galt5a8EH_CKmEsKVf1_eMrR7UlxNoYI {"cookie": {"path": "/", "secure": false, "expires": "2025-07-20T10:02:28.332Z", "httpOnly": true, "originalMaxAge": 86400000}, "userId": 1} 2025-07-20 12:25:32
|
||||
\.
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: stores; Type: TABLE DATA; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
COPY public.stores (id, name, address, phone, email, is_active, created_at, updated_at) FROM stdin;
|
||||
1 Frouard À définir t 2025-07-09 18:37:53.432996 2025-07-09 18:37:53.432996
|
||||
2 Houdemont t 2025-07-09 19:00:30.782965 2025-07-09 19:00:30.782965
|
||||
\.
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: users; Type: TABLE DATA; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
COPY public.users (id, username, password, email, first_name, last_name, role, store_id, is_active, created_at, updated_at) FROM stdin;
|
||||
2 FFNANCY michaelschal@gmail.com manager 1 t 2025-07-09 18:55:01.413195 2025-07-09 18:55:01.413195
|
||||
3 gael $2b$12$G0BTV6XVgO5B9DdWeU4nzO8N6x1m7jsEUw2VUnrb2G4mmq8Tj1lVi \N administrator \N t 2025-07-10 08:47:04.063397 2025-07-10 08:47:04.063397
|
||||
4 ff292 $2b$12$vQYWysmorDWUUp6bXsqMHOj//087IiwIZQzkB9gABqXFyISaiqZMK \N employee 1 t 2025-07-10 09:48:06.549645 2025-07-10 09:48:06.549645
|
||||
5 ff579 $2b$12$wKO1Ihm44GcsaSaAKFfD7uO7q8gCzTNbG7vdW0SFT8Z7RGshO9o2W \N employee 2 t 2025-07-10 09:48:22.256912 2025-07-10 09:48:22.256912
|
||||
1 admin $2b$12$Q6bxVsV0.uE78jgh9LxnYukLB6oS9jPkFeIVdrlkAM5Dwr7YQ5qTK michaelschal@ffest.fr Administrateur Principal admin 1 t 2025-07-09 18:37:53.865533 2025-07-09 18:37:53.865533
|
||||
\.
|
||||
|
||||
|
||||
--
|
||||
-- Name: sale_products_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.sale_products_id_seq', 17, true);
|
||||
|
||||
|
||||
--
|
||||
-- Name: sales_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.sales_id_seq', 17, true);
|
||||
|
||||
|
||||
--
|
||||
-- Name: stores_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.stores_id_seq', 2, true);
|
||||
|
||||
|
||||
--
|
||||
-- Name: users_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.users_id_seq', 5, true);
|
||||
|
||||
|
||||
--
|
||||
-- Name: sale_products sale_products_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.sale_products
|
||||
ADD CONSTRAINT sale_products_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: sales sales_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.sales
|
||||
ADD CONSTRAINT sales_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: sessions sessions_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.sessions
|
||||
ADD CONSTRAINT sessions_pkey PRIMARY KEY (sid);
|
||||
|
||||
|
||||
--
|
||||
-- Name: stores stores_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.stores
|
||||
ADD CONSTRAINT stores_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: users users_email_unique; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.users
|
||||
ADD CONSTRAINT users_email_unique UNIQUE (email);
|
||||
|
||||
|
||||
--
|
||||
-- Name: users users_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.users
|
||||
ADD CONSTRAINT users_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: users users_username_unique; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.users
|
||||
ADD CONSTRAINT users_username_unique UNIQUE (username);
|
||||
|
||||
|
||||
--
|
||||
-- Name: IDX_session_expire; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX "IDX_session_expire" ON public.sessions USING btree (expire);
|
||||
|
||||
|
||||
--
|
||||
-- Name: sale_products sale_products_sale_id_sales_id_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.sale_products
|
||||
ADD CONSTRAINT sale_products_sale_id_sales_id_fk FOREIGN KEY (sale_id) REFERENCES public.sales(id) ON DELETE CASCADE;
|
||||
|
||||
|
||||
--
|
||||
-- Name: sales sales_store_id_stores_id_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.sales
|
||||
ADD CONSTRAINT sales_store_id_stores_id_fk FOREIGN KEY (store_id) REFERENCES public.stores(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: sales sales_user_id_users_id_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.sales
|
||||
ADD CONSTRAINT sales_user_id_users_id_fk FOREIGN KEY (user_id) REFERENCES public.users(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: users users_store_id_stores_id_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.users
|
||||
ADD CONSTRAINT users_store_id_stores_id_fk FOREIGN KEY (store_id) REFERENCES public.stores(id);
|
||||
|
||||
|
||||
--
|
||||
-- PostgreSQL database dump complete
|
||||
--
|
||||
|
||||
@@ -308,52 +308,44 @@ export default function Administration() {
|
||||
deleteStoreMutation.mutate(store.id);
|
||||
};
|
||||
|
||||
// SQL Backup mutations
|
||||
// Backup mutations
|
||||
const exportBackupMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
const response = await fetch('/api/admin/backup/export', {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to export SQL backup');
|
||||
}
|
||||
|
||||
return response.text(); // Get SQL content as text
|
||||
const response = await apiRequest('/api/admin/backup/export');
|
||||
return response.json();
|
||||
},
|
||||
onSuccess: (sqlContent) => {
|
||||
// Create and download SQL file
|
||||
const blob = new Blob([sqlContent], { type: 'application/sql' });
|
||||
onSuccess: (data) => {
|
||||
// Créer et télécharger le fichier JSON
|
||||
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `backup_${new Date().toISOString().split('T')[0]}_${Date.now()}.sql`;
|
||||
link.download = `backup_${new Date().toISOString().split('T')[0]}_${Date.now()}.json`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
toast({
|
||||
title: "Sauvegarde SQL exportée",
|
||||
description: "Le fichier de sauvegarde SQL a été téléchargé",
|
||||
title: "Sauvegarde exportée",
|
||||
description: "Le fichier de sauvegarde a été téléchargé",
|
||||
variant: "success",
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
onError: () => {
|
||||
toast({
|
||||
title: "Erreur d'export SQL",
|
||||
description: error instanceof Error ? error.message : "Impossible d'exporter la sauvegarde SQL",
|
||||
title: "Erreur",
|
||||
description: "Impossible d'exporter la sauvegarde",
|
||||
variant: "destructive",
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const importBackupMutation = useMutation({
|
||||
mutationFn: async (sqlContent: string) => {
|
||||
mutationFn: async (backupData: any) => {
|
||||
const response = await apiRequest('/api/admin/backup/import', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ sqlContent }),
|
||||
body: JSON.stringify(backupData),
|
||||
});
|
||||
return response.json();
|
||||
},
|
||||
@@ -361,15 +353,15 @@ export default function Administration() {
|
||||
// Invalider toutes les queries pour forcer un refresh
|
||||
queryClient.clear();
|
||||
toast({
|
||||
title: "Sauvegarde SQL importée",
|
||||
description: "La base de données a été restaurée avec succès",
|
||||
title: "Sauvegarde importée",
|
||||
description: "Les données ont été restaurées avec succès",
|
||||
variant: "success",
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
onError: () => {
|
||||
toast({
|
||||
title: "Erreur d'import SQL",
|
||||
description: error instanceof Error ? error.message : "Impossible d'importer la sauvegarde SQL",
|
||||
title: "Erreur",
|
||||
description: "Impossible d'importer la sauvegarde",
|
||||
variant: "destructive",
|
||||
});
|
||||
},
|
||||
@@ -406,22 +398,19 @@ export default function Administration() {
|
||||
const handleImportBackup = () => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.sql';
|
||||
input.accept = '.json';
|
||||
input.onchange = (event) => {
|
||||
const file = (event.target as HTMLInputElement).files?.[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const sqlContent = e.target?.result as string;
|
||||
if (!sqlContent.trim()) {
|
||||
throw new Error('Le fichier SQL est vide');
|
||||
}
|
||||
importBackupMutation.mutate(sqlContent);
|
||||
const backupData = JSON.parse(e.target?.result as string);
|
||||
importBackupMutation.mutate(backupData);
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "Erreur",
|
||||
description: error instanceof Error ? error.message : "Fichier de sauvegarde SQL invalide",
|
||||
description: "Fichier de sauvegarde invalide",
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
@@ -1235,7 +1224,7 @@ export default function Administration() {
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Téléchargez une sauvegarde SQL complète de toutes les données (utilisateurs, magasins, ventes) compatible avec PostgreSQL.
|
||||
Téléchargez une sauvegarde complète de toutes les données (utilisateurs, magasins, ventes).
|
||||
</p>
|
||||
<Button
|
||||
onClick={handleExportBackup}
|
||||
@@ -1243,7 +1232,7 @@ export default function Administration() {
|
||||
className="w-full"
|
||||
>
|
||||
<Download className="h-4 w-4 mr-2" />
|
||||
{exportBackupMutation.isPending ? "Export SQL en cours..." : "Télécharger la Sauvegarde SQL"}
|
||||
{exportBackupMutation.isPending ? "Export en cours..." : "Télécharger la Sauvegarde"}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -1258,7 +1247,7 @@ export default function Administration() {
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Restaurez la base de données à partir d'un fichier de sauvegarde SQL PostgreSQL.
|
||||
Restaurez les données à partir d'un fichier de sauvegarde.
|
||||
<strong className="text-red-600"> Attention : cette action remplacera toutes les données existantes.</strong>
|
||||
</p>
|
||||
<AlertDialog>
|
||||
@@ -1269,18 +1258,18 @@ export default function Administration() {
|
||||
className="w-full"
|
||||
>
|
||||
<Upload className="h-4 w-4 mr-2" />
|
||||
{importBackupMutation.isPending ? "Import SQL en cours..." : "Restaurer depuis un Fichier SQL"}
|
||||
{importBackupMutation.isPending ? "Import en cours..." : "Restaurer depuis un Fichier"}
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Confirmer la restauration SQL</AlertDialogTitle>
|
||||
<AlertDialogTitle>Confirmer la restauration</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Êtes-vous sûr de vouloir restaurer la base de données depuis un fichier de sauvegarde SQL ?
|
||||
Êtes-vous sûr de vouloir restaurer les données depuis un fichier de sauvegarde ?
|
||||
<br /><br />
|
||||
<strong className="text-red-600">
|
||||
⚠️ ATTENTION : Cette action exécutera le script SQL qui remplacera complètement
|
||||
la structure et les données de la base de données PostgreSQL.
|
||||
⚠️ ATTENTION : Cette action supprimera définitivement toutes les données actuelles
|
||||
(utilisateurs, magasins, ventes) et les remplacera par celles du fichier de sauvegarde.
|
||||
</strong>
|
||||
<br /><br />
|
||||
Cette action est irréversible. Assurez-vous d'avoir une sauvegarde des données actuelles
|
||||
@@ -1293,7 +1282,7 @@ export default function Administration() {
|
||||
onClick={handleImportBackup}
|
||||
className="bg-red-600 hover:bg-red-700"
|
||||
>
|
||||
Confirmer la Restauration SQL
|
||||
Confirmer la Restauration
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
@@ -1310,19 +1299,16 @@ export default function Administration() {
|
||||
<CardContent>
|
||||
<div className="space-y-3 text-sm">
|
||||
<div>
|
||||
<strong>Sauvegarde automatique :</strong> Le système crée automatiquement une sauvegarde SQL toutes les 12 heures (00:00 et 12:00) et conserve les 10 plus récentes.
|
||||
<strong>Sauvegarde automatique :</strong> Le système crée automatiquement une sauvegarde toutes les 12 heures (00:00 et 12:00) et conserve les 10 plus récentes.
|
||||
</div>
|
||||
<div>
|
||||
<strong>Format des fichiers :</strong> Les sauvegardes sont au format SQL PostgreSQL (.sql) et contiennent la structure complète et les données de la base.
|
||||
<strong>Format des fichiers :</strong> Les sauvegardes sont au format JSON et contiennent toutes les données de l'application.
|
||||
</div>
|
||||
<div>
|
||||
<strong>Contenu inclus :</strong> Schéma de base de données, utilisateurs, magasins, ventes, et toutes les relations entre ces données.
|
||||
<strong>Contenu inclus :</strong> Utilisateurs, magasins, ventes, et toutes les relations entre ces données.
|
||||
</div>
|
||||
<div>
|
||||
<strong>Avantages SQL :</strong> Sauvegarde native PostgreSQL compatible avec pg_dump/pg_restore, structure et données préservées.
|
||||
</div>
|
||||
<div>
|
||||
<strong>Restauration :</strong> Remplace complètement la base de données existante par le contenu du fichier SQL importé.
|
||||
<strong>Sécurité :</strong> Les mots de passe sont hachés et ne peuvent pas être récupérés depuis une sauvegarde.
|
||||
</div>
|
||||
<div>
|
||||
<strong>Recommandation :</strong> Les sauvegardes automatiques couvrent la plupart des besoins, mais vous pouvez créer des sauvegardes manuelles avant des modifications importantes.
|
||||
|
||||
@@ -64,7 +64,7 @@ export default function NewSaleForm() {
|
||||
const form = useForm<FormData>({
|
||||
defaultValues: {
|
||||
vendeur: '',
|
||||
products: [{ typeArticle: '', categorie: '', quantite: '1', gencode: '' }],
|
||||
products: [{ typeArticle: '', categorie: '', quantite: '', gencode: '' }],
|
||||
nom: '',
|
||||
prenom: '',
|
||||
dateNaissance: '',
|
||||
@@ -85,7 +85,7 @@ export default function NewSaleForm() {
|
||||
|
||||
const addProduct = () => {
|
||||
const currentProducts = form.getValues('products');
|
||||
form.setValue('products', [...currentProducts, { typeArticle: '', categorie: '', quantite: '1', gencode: '' }]);
|
||||
form.setValue('products', [...currentProducts, { typeArticle: '', categorie: '', quantite: '', gencode: '' }]);
|
||||
};
|
||||
|
||||
const removeProduct = (index: number) => {
|
||||
@@ -370,7 +370,7 @@ export default function NewSaleForm() {
|
||||
<div className="section-vendor">
|
||||
<div className="section-header">
|
||||
<User className="h-5 w-5" />
|
||||
<span>Informations vendeur</span>
|
||||
<span>Informations Vendeur</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<FormField
|
||||
@@ -378,7 +378,7 @@ export default function NewSaleForm() {
|
||||
name="vendeur"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Nom du vendeur <span className="text-destructive">*</span></FormLabel>
|
||||
<FormLabel>Vendeur <span className="text-destructive">*</span></FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Nom du vendeur" className="modern-input" {...field} />
|
||||
</FormControl>
|
||||
@@ -389,14 +389,11 @@ export default function NewSaleForm() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Section Produits */}
|
||||
{/* Section Produit */}
|
||||
<div className="section-product">
|
||||
<div className="section-header">
|
||||
<Package className="h-5 w-5" />
|
||||
<span>Produits</span>
|
||||
{products.length > 1 && (
|
||||
<span className="text-sm text-gray-600 ml-2">({products.length} produits)</span>
|
||||
)}
|
||||
<span>Informations Produit</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<FormField
|
||||
@@ -457,7 +454,7 @@ export default function NewSaleForm() {
|
||||
<FormItem>
|
||||
<FormLabel>Quantité <span className="text-destructive">*</span></FormLabel>
|
||||
<FormControl>
|
||||
<Input type="number" min="1" placeholder="1" className="modern-input" {...field} />
|
||||
<Input type="number" min="1" placeholder="Entrez la quantité" className="modern-input" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -490,7 +487,7 @@ export default function NewSaleForm() {
|
||||
<div className="section-client">
|
||||
<div className="section-header">
|
||||
<Users className="h-5 w-5" />
|
||||
<span>Informations client</span>
|
||||
<span>Informations Client</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<FormField
|
||||
@@ -576,7 +573,7 @@ export default function NewSaleForm() {
|
||||
<div className="section-identity">
|
||||
<div className="section-header">
|
||||
<BadgeCheck className="h-5 w-5" />
|
||||
<span>Pièce d'identité</span>
|
||||
<span>Pièce d'Identité</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<FormField
|
||||
@@ -647,8 +644,7 @@ export default function NewSaleForm() {
|
||||
{/* Section Photos */}
|
||||
<div className="mt-6 space-y-4">
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-gray-700">Photos de la pièce d'identité (optionnel)</h4>
|
||||
<p className="text-xs text-gray-500 mt-1">Les photos d'identité ne sont pas obligatoires</p>
|
||||
<h4 className="text-sm font-medium text-gray-700">Photos de la pièce d'identité</h4>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
@@ -759,8 +755,7 @@ export default function NewSaleForm() {
|
||||
{/* Photo Ticket de caisse */}
|
||||
<div className="mt-6 space-y-4">
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-gray-700">Photo du ticket de caisse (optionnel)</h4>
|
||||
<p className="text-xs text-gray-500 mt-1">La photo du ticket n'est pas obligatoire</p>
|
||||
<h4 className="text-sm font-medium text-gray-700">Photo du ticket de caisse</h4>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
|
||||
@@ -409,21 +409,21 @@ body {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
/* Fonds de couleur unis harmonisés selon la nouvelle interface */
|
||||
/* Fonds de couleur unis harmonisés avec des teintes distinctes */
|
||||
.section-vendor {
|
||||
background: #e0f2fe !important; /* Fond bleu clair comme l'image */
|
||||
background: #f0f9ff !important; /* Fond bleu très clair */
|
||||
}
|
||||
|
||||
.section-product {
|
||||
background: #e8f5e8 !important; /* Fond vert clair comme l'image */
|
||||
background: #f0fdf4 !important; /* Fond vert très clair */
|
||||
}
|
||||
|
||||
.section-client {
|
||||
background: #f3e8ff !important; /* Fond violet clair comme l'image */
|
||||
background: #fef3c7 !important; /* Fond jaune clair */
|
||||
}
|
||||
|
||||
.section-identity {
|
||||
background: #fff3e0 !important; /* Fond orange clair comme l'image */
|
||||
background: #fce7f3 !important; /* Fond rose très clair */
|
||||
}
|
||||
|
||||
/* S'assurer que tous les inputs ont un fond blanc dans toutes les sections */
|
||||
|
||||
@@ -315,28 +315,6 @@ Preferred communication style: Simple, everyday language.
|
||||
- ✅ **Database Schema**: Sales and sale_products tables properly configured for multi-product sales
|
||||
- ✅ **Export Compatibility**: All export functions (Excel, PDF, CSV) support multiple products per sale
|
||||
|
||||
### SQL Backup System Implementation (January 19, 2025)
|
||||
- ✅ **SQL Backup Format**: Migrated from JSON to native PostgreSQL SQL format using pg_dump
|
||||
- ✅ **Database Restoration**: Implemented SQL import capability using psql for complete database restoration
|
||||
- ✅ **Automatic SQL Backups**: Modified scheduler to create SQL dumps instead of JSON exports
|
||||
- ✅ **Manual Export/Import**: Updated administration interface to handle SQL files (.sql)
|
||||
- ✅ **Buffer Optimization**: Increased maxBuffer to 50MB for handling large databases with photo data
|
||||
- ✅ **Production Compatibility**: Native PostgreSQL backup format ensures full compatibility with production systems
|
||||
- ✅ **Data Integrity**: SQL format preserves all database structure, constraints, and relationships
|
||||
- ✅ **User Interface**: Updated backup management UI with SQL-specific messaging and file extensions
|
||||
|
||||
### Photo Requirements Optimization (January 19, 2025)
|
||||
- ✅ **Optional Identity Photos**: Made identity document photos (recto/verso) optional as per user request
|
||||
- ✅ **Optional Receipt Photos**: Clarified that receipt/ticket photos are also optional
|
||||
- ✅ **UI Updates**: Updated form labels to clearly indicate photo fields are not mandatory
|
||||
- ✅ **Validation Adjustment**: Photos no longer required for form submission compliance
|
||||
- ✅ **User Experience**: Added explanatory text to reduce confusion about photo requirements
|
||||
|
||||
### Form Usability Improvements (January 19, 2025)
|
||||
- ✅ **Default Quantity**: Set product quantity field to default value of 1 for better user experience
|
||||
- ✅ **Streamlined Input**: Users no longer need to manually enter quantity for single item purchases
|
||||
- ✅ **Placeholder Update**: Changed placeholder text to reflect default value
|
||||
|
||||
### Docker Configuration & Cleanup (January 9, 2025)
|
||||
- ✅ Complete Docker setup optimized for external PostgreSQL database
|
||||
- ✅ Docker Compose configuration with RegisFlow application only
|
||||
|
||||
+42
-105
@@ -2,22 +2,10 @@ import cron from 'node-cron';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { storage } from './storage';
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
const BACKUP_DIR = path.join(process.cwd(), 'backups');
|
||||
const MAX_BACKUPS = 10;
|
||||
|
||||
// Database connection info from environment variables
|
||||
const DB_HOST = process.env.PGHOST || 'localhost';
|
||||
const DB_PORT = process.env.PGPORT || '5432';
|
||||
const DB_NAME = process.env.PGDATABASE || 'postgres';
|
||||
const DB_USER = process.env.PGUSER || 'postgres';
|
||||
const DB_PASSWORD = process.env.PGPASSWORD || '';
|
||||
const DATABASE_URL = process.env.DATABASE_URL;
|
||||
|
||||
// Ensure backup directory exists
|
||||
if (!fs.existsSync(BACKUP_DIR)) {
|
||||
fs.mkdirSync(BACKUP_DIR, { recursive: true });
|
||||
@@ -25,117 +13,66 @@ if (!fs.existsSync(BACKUP_DIR)) {
|
||||
|
||||
export async function createAutomaticBackup() {
|
||||
try {
|
||||
console.log('Creating automatic SQL backup...');
|
||||
console.log('Creating automatic backup...');
|
||||
|
||||
// Create filename with timestamp
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const filename = `auto-backup-${timestamp}.sql`;
|
||||
const filepath = path.join(BACKUP_DIR, filename);
|
||||
|
||||
// Create SQL dump using pg_dump
|
||||
await createSQLBackup(filepath);
|
||||
|
||||
// Get stats for logging
|
||||
// Get all data from the database
|
||||
const users = await storage.getAllUsers();
|
||||
const stores = await storage.getAllStores();
|
||||
let totalSales = 0;
|
||||
|
||||
// Get all sales from all stores
|
||||
const allSales = [];
|
||||
for (const store of stores) {
|
||||
const storeSales = await storage.getSalesByStore(store.id);
|
||||
totalSales += storeSales.length;
|
||||
allSales.push(...storeSales);
|
||||
}
|
||||
|
||||
const backupData = {
|
||||
timestamp: new Date().toISOString(),
|
||||
version: "1.0",
|
||||
type: "automatic",
|
||||
data: {
|
||||
users: users.map(user => ({
|
||||
...user,
|
||||
// Don't include the password hash in backup for security
|
||||
password: undefined
|
||||
})),
|
||||
stores,
|
||||
sales: allSales
|
||||
},
|
||||
metadata: {
|
||||
totalUsers: users.length,
|
||||
totalStores: stores.length,
|
||||
totalSales: allSales.length,
|
||||
exportedBy: 'system-auto-backup'
|
||||
}
|
||||
};
|
||||
|
||||
// Create filename with timestamp
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const filename = `auto-backup-${timestamp}.json`;
|
||||
const filepath = path.join(BACKUP_DIR, filename);
|
||||
|
||||
// Write backup to file
|
||||
fs.writeFileSync(filepath, JSON.stringify(backupData, null, 2));
|
||||
|
||||
console.log(`✅ Automatic SQL backup created: ${filename}`);
|
||||
console.log(`📊 Backup stats: ${users.length} users, ${stores.length} stores, ${totalSales} sales`);
|
||||
console.log(`✅ Automatic backup created: ${filename}`);
|
||||
console.log(`📊 Backup stats: ${users.length} users, ${stores.length} stores, ${allSales.length} sales`);
|
||||
|
||||
// Clean up old backups
|
||||
await cleanupOldBackups();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
filename,
|
||||
stats: {
|
||||
totalUsers: users.length,
|
||||
totalStores: stores.length,
|
||||
totalSales: totalSales,
|
||||
exportedBy: 'system-auto-backup'
|
||||
}
|
||||
};
|
||||
return { success: true, filename, stats: backupData.metadata };
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to create automatic SQL backup:', error);
|
||||
console.error('❌ Failed to create automatic backup:', error);
|
||||
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
|
||||
}
|
||||
}
|
||||
|
||||
export async function createSQLBackup(filepath: string): Promise<void> {
|
||||
try {
|
||||
// Use pg_dump to create SQL backup
|
||||
let command: string;
|
||||
|
||||
if (DATABASE_URL) {
|
||||
// Use DATABASE_URL if available
|
||||
command = `pg_dump "${DATABASE_URL}" --no-owner --no-privileges --clean --if-exists`;
|
||||
} else {
|
||||
// Use individual connection parameters
|
||||
command = `PGPASSWORD="${DB_PASSWORD}" pg_dump -h "${DB_HOST}" -p "${DB_PORT}" -U "${DB_USER}" -d "${DB_NAME}" --no-owner --no-privileges --clean --if-exists`;
|
||||
}
|
||||
|
||||
const { stdout, stderr } = await execAsync(command, {
|
||||
maxBuffer: 50 * 1024 * 1024, // 50MB buffer for large databases
|
||||
});
|
||||
|
||||
if (stderr && !stderr.includes('NOTICE')) {
|
||||
console.warn('pg_dump warnings:', stderr);
|
||||
}
|
||||
|
||||
// Write SQL dump to file
|
||||
fs.writeFileSync(filepath, stdout);
|
||||
|
||||
console.log(`SQL backup created: ${path.basename(filepath)}`);
|
||||
} catch (error) {
|
||||
console.error('Failed to create SQL backup:', error);
|
||||
throw new Error(`SQL backup failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function restoreSQLBackup(filepath: string): Promise<void> {
|
||||
try {
|
||||
console.log(`Restoring SQL backup from: ${path.basename(filepath)}`);
|
||||
|
||||
// Read SQL file
|
||||
const sqlContent = fs.readFileSync(filepath, 'utf8');
|
||||
|
||||
// Use psql to restore the backup
|
||||
let command: string;
|
||||
|
||||
if (DATABASE_URL) {
|
||||
// Use DATABASE_URL if available
|
||||
command = `psql "${DATABASE_URL}"`;
|
||||
} else {
|
||||
// Use individual connection parameters
|
||||
command = `PGPASSWORD="${DB_PASSWORD}" psql -h "${DB_HOST}" -p "${DB_PORT}" -U "${DB_USER}" -d "${DB_NAME}"`;
|
||||
}
|
||||
|
||||
const { stderr } = await execAsync(command, {
|
||||
input: sqlContent,
|
||||
maxBuffer: 50 * 1024 * 1024, // 50MB buffer for large databases
|
||||
});
|
||||
|
||||
if (stderr && !stderr.includes('NOTICE')) {
|
||||
console.warn('psql warnings:', stderr);
|
||||
}
|
||||
|
||||
console.log(`✅ SQL backup restored successfully from: ${path.basename(filepath)}`);
|
||||
} catch (error) {
|
||||
console.error('Failed to restore SQL backup:', error);
|
||||
throw new Error(`SQL restore failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function cleanupOldBackups() {
|
||||
try {
|
||||
// Get all backup files (now SQL files)
|
||||
// Get all backup files
|
||||
const files = fs.readdirSync(BACKUP_DIR)
|
||||
.filter(file => file.startsWith('auto-backup-') && file.endsWith('.sql'))
|
||||
.filter(file => file.startsWith('auto-backup-') && file.endsWith('.json'))
|
||||
.map(file => ({
|
||||
name: file,
|
||||
path: path.join(BACKUP_DIR, file),
|
||||
@@ -162,7 +99,7 @@ async function cleanupOldBackups() {
|
||||
export function getBackupStats() {
|
||||
try {
|
||||
const files = fs.readdirSync(BACKUP_DIR)
|
||||
.filter(file => file.startsWith('auto-backup-') && file.endsWith('.sql'));
|
||||
.filter(file => file.startsWith('auto-backup-') && file.endsWith('.json'));
|
||||
|
||||
return {
|
||||
totalBackups: files.length,
|
||||
|
||||
+118
-46
@@ -5,9 +5,7 @@ import session from "express-session";
|
||||
import connectPg from "connect-pg-simple";
|
||||
import { loginSchema, insertUserSchema, insertStoreSchema, insertSaleSchema } from "@shared/schema";
|
||||
import { z } from "zod";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { createAutomaticBackup, getBackupStats, createSQLBackup, restoreSQLBackup } from "./backup-scheduler";
|
||||
import { createAutomaticBackup, getBackupStats } from "./backup-scheduler";
|
||||
import { executePurgeManually, getPurgeStats } from "./data-purge";
|
||||
|
||||
declare module 'express-session' {
|
||||
@@ -466,65 +464,139 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
}
|
||||
});
|
||||
|
||||
// SQL Backup routes
|
||||
// Backup routes
|
||||
app.get('/api/admin/backup/export', requireRole(['admin']), async (req, res) => {
|
||||
try {
|
||||
// Create a temporary SQL backup
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const filename = `manual-backup-${timestamp}.sql`;
|
||||
const filepath = path.join(process.cwd(), 'backups', filename);
|
||||
|
||||
await createSQLBackup(filepath);
|
||||
|
||||
// Send the SQL file
|
||||
res.setHeader('Content-Type', 'application/sql');
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
||||
|
||||
const sqlContent = fs.readFileSync(filepath, 'utf8');
|
||||
res.send(sqlContent);
|
||||
|
||||
// Optionally delete the temporary file after sending
|
||||
// fs.unlinkSync(filepath);
|
||||
// Get all data from the database
|
||||
const users = await storage.getAllUsers();
|
||||
const stores = await storage.getAllStores();
|
||||
|
||||
// Get all sales from all stores
|
||||
const allSales = [];
|
||||
for (const store of stores) {
|
||||
const storeSales = await storage.getSalesByStore(store.id);
|
||||
allSales.push(...storeSales);
|
||||
}
|
||||
|
||||
const backupData = {
|
||||
timestamp: new Date().toISOString(),
|
||||
version: "1.0",
|
||||
data: {
|
||||
users: users.map(user => ({
|
||||
...user,
|
||||
// Don't include the password hash in backup for security
|
||||
password: undefined
|
||||
})),
|
||||
stores,
|
||||
sales: allSales
|
||||
},
|
||||
metadata: {
|
||||
totalUsers: users.length,
|
||||
totalStores: stores.length,
|
||||
totalSales: allSales.length,
|
||||
exportedBy: req.user.username
|
||||
}
|
||||
};
|
||||
|
||||
res.json(backupData);
|
||||
} catch (error) {
|
||||
console.error('Export SQL backup error:', error);
|
||||
res.status(500).json({ error: "Failed to export SQL backup" });
|
||||
console.error('Export backup error:', error);
|
||||
res.status(500).json({ error: "Failed to export backup" });
|
||||
}
|
||||
});
|
||||
|
||||
// SQL Backup import route (receives SQL file content)
|
||||
app.post('/api/admin/backup/import', requireRole(['admin']), async (req, res) => {
|
||||
try {
|
||||
const { sqlContent } = req.body;
|
||||
const backupData = req.body;
|
||||
|
||||
if (!sqlContent || typeof sqlContent !== 'string') {
|
||||
return res.status(400).json({ error: "SQL content is required" });
|
||||
// Validate backup structure
|
||||
if (!backupData.data || !backupData.data.users || !backupData.data.stores || !backupData.data.sales) {
|
||||
return res.status(400).json({ error: "Invalid backup file format" });
|
||||
}
|
||||
|
||||
// Create a temporary file for the SQL content
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const filename = `restore-${timestamp}.sql`;
|
||||
const filepath = path.join(process.cwd(), 'backups', filename);
|
||||
|
||||
// Write SQL content to temporary file
|
||||
fs.writeFileSync(filepath, sqlContent);
|
||||
|
||||
// Restore the backup
|
||||
await restoreSQLBackup(filepath);
|
||||
|
||||
// Clean up temporary file
|
||||
fs.unlinkSync(filepath);
|
||||
// Clear existing data (in reverse order due to foreign keys)
|
||||
// First delete all sales
|
||||
const existingSales = [];
|
||||
const existingStores = await storage.getAllStores();
|
||||
for (const store of existingStores) {
|
||||
const storeSales = await storage.getSalesByStore(store.id);
|
||||
existingSales.push(...storeSales);
|
||||
}
|
||||
|
||||
for (const sale of existingSales) {
|
||||
await storage.deleteSale(sale.id);
|
||||
}
|
||||
|
||||
// Then delete users (except admin to avoid locking out)
|
||||
const existingUsers = await storage.getAllUsers();
|
||||
for (const user of existingUsers) {
|
||||
if (user.role !== 'administrator' || user.username !== 'admin') {
|
||||
await storage.deleteUser(user.id);
|
||||
}
|
||||
}
|
||||
|
||||
// Then delete stores
|
||||
for (const store of existingStores) {
|
||||
await storage.deleteStore(store.id);
|
||||
}
|
||||
|
||||
// Import new data
|
||||
// First import stores
|
||||
const storeIdMapping = new Map();
|
||||
for (const storeData of backupData.data.stores) {
|
||||
const { id, ...storeWithoutId } = storeData;
|
||||
const newStore = await storage.createStore(storeWithoutId);
|
||||
storeIdMapping.set(id, newStore.id);
|
||||
}
|
||||
|
||||
// Then import users (excluding admin and users with passwords)
|
||||
const userIdMapping = new Map();
|
||||
for (const userData of backupData.data.users) {
|
||||
if (userData.username === 'admin') continue; // Skip admin user
|
||||
|
||||
const { id, password, ...userWithoutIdAndPassword } = userData;
|
||||
|
||||
// Map store ID if it exists
|
||||
if (userData.storeId && storeIdMapping.has(userData.storeId)) {
|
||||
userWithoutIdAndPassword.storeId = storeIdMapping.get(userData.storeId);
|
||||
}
|
||||
|
||||
// Set a default password since we can't restore the original
|
||||
const userWithPassword = {
|
||||
...userWithoutIdAndPassword,
|
||||
password: 'restored123' // Users will need to change this
|
||||
};
|
||||
|
||||
const newUser = await storage.createUser(userWithPassword);
|
||||
userIdMapping.set(id, newUser.id);
|
||||
}
|
||||
|
||||
// Finally import sales
|
||||
for (const saleData of backupData.data.sales) {
|
||||
const { id, ...saleWithoutId } = saleData;
|
||||
|
||||
// Map store and user IDs
|
||||
if (saleData.storeId && storeIdMapping.has(saleData.storeId)) {
|
||||
saleWithoutId.storeId = storeIdMapping.get(saleData.storeId);
|
||||
}
|
||||
if (saleData.userId && userIdMapping.has(saleData.userId)) {
|
||||
saleWithoutId.userId = userIdMapping.get(saleData.userId);
|
||||
}
|
||||
|
||||
await storage.createSale(saleWithoutId);
|
||||
}
|
||||
|
||||
res.json({
|
||||
message: "SQL backup restored successfully",
|
||||
timestamp: new Date().toISOString()
|
||||
message: "Backup imported successfully",
|
||||
imported: {
|
||||
stores: backupData.data.stores.length,
|
||||
users: backupData.data.users.filter((u: any) => u.username !== 'admin').length,
|
||||
sales: backupData.data.sales.length
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Import SQL backup error:', error);
|
||||
res.status(500).json({
|
||||
error: "Failed to restore SQL backup",
|
||||
details: error instanceof Error ? error.message : 'Unknown error'
|
||||
});
|
||||
console.error('Import backup error:', error);
|
||||
res.status(500).json({ error: "Failed to import backup", details: error instanceof Error ? error.message : 'Unknown error' });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user