summaryrefslogtreecommitdiff
path: root/crawler/database.go
blob: c5e2291e86c51ae1d11b625ab8912e80f288b86e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
package main

import (
	"database/sql"
	"fmt"
	"strings"
)

func (app *App) createTables() error {
	query1 := `CREATE TABLE IF NOT EXISTS shop(
		id INTEGER PRIMARY KEY AUTO_INCREMENT,
		name varchar(255) UNIQUE NOT NULL,
		url varchar(255) UNIQUE NOT NULL,
		short_url varchar(255) UNIQUE,
		logo_url text,
		shipping_costs int,
		free_shipping text
	)`
	_, err := app.DB.Exec(query1)
	if err != nil {
		return err
	}

	query2 := `CREATE TABLE IF NOT EXISTS angebot (
		id INTEGER PRIMARY KEY AUTO_INCREMENT,
		name VARCHAR(255) NOT NULL,
		abv DECIMAL(10,2) NOT NULL,
		volume DECIMAL(10,2) NOT NULL,
		age INT NOT NULL DEFAULT 0,
		shop INT NOT NULL,
		url VARCHAR(255) NOT NULL,
		short_url TEXT,
		original_price INT NOT NULL,
		discounted_price INT NOT NULL,
		base_price INT NOT NULL,
		image_url TEXT,
		spirit_type TEXT NOT NULL,
		website TEXT,
		website_raw TEXT NOT NULL,
		valid_until INT NOT NULL DEFAULT 0,
		created_at INT,
		FOREIGN KEY(shop) REFERENCES shop(id),
		UNIQUE(name,shop,volume,abv,original_price,discounted_price,valid_until),
		UNIQUE(name,shop,url,created_at)
	)`
	_, err = app.DB.Exec(query2)
	if err != nil {
		return err
	}

	views := []string{"whisky", "wodka", "rum", "gin", "misc", "all"}

	for _, v := range views {
		var spirit_type string
		if v == "misc" {
			spirit_type = `spirit_type NOT IN ("` + strings.Join(views, `", "`) + `") AND`
		} else if v == "all" {
			spirit_type = "1 = 1 AND "
		} else {
			spirit_type = `spirit_type = "` + v + `" AND`
		}

		view_query := `CREATE OR REPLACE VIEW ` + v + `_view AS
		SELECT
			angebot.id, angebot.name, age, angebot.abv, angebot.volume, angebot.url as long_url, angebot.short_url as url, spirit_type,
			original_price/100 as original_price, discounted_price/100 as discounted_price, angebot.base_price/100 as base_price, image_url,
			shop.name as shop, shop.id as shop_id, shop.short_url as shop_url, shop.shipping_costs/100 as shipping_costs, shop.free_shipping,
			ROUND(100-((discounted_price/original_price)*100)) AS procent, created_at
		FROM angebot
		JOIN shop ON angebot.shop = shop.id
		WHERE 
			` + spirit_type + ` (valid_until = 0 OR valid_until > (SELECT UNIX_TIMESTAMP()))`
		_, err = app.DB.Exec(view_query)
		if err != nil {
			return err
		}
	}

	view_query := `CREATE OR REPLACE VIEW _intern_view AS SELECT angebot.id, angebot.name, angebot.abv, angebot.volume, angebot.url as long_url, angebot.short_url as url, spirit_type, original_price, discounted_price, base_price, shop.name as shop, shop.id as shop_id, shop.short_url as shop_url, created_at, valid_until
		FROM angebot
		JOIN shop ON angebot.shop = shop.id
		WHERE (valid_until = 0 OR valid_until > (SELECT UNIX_TIMESTAMP()))`
	_, err = app.DB.Exec(view_query)
	if err != nil {
		return err
	}

	return err
}

func (app *App) save_offer(W []Angebot) error {

	query := `INSERT INTO angebot (shop, name, url, abv, volume, age, original_price, discounted_price, base_price, valid_until, image_url, website_raw, spirit_type, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`

	stmt, err := app.DB.Prepare(query)
	if err != nil {
		Debug(err, "Save Offer: Preparing query failed")
		return err
	}
	defer stmt.Close()

	for _, o := range W {

		var found int

		if o.Discounted_price == 0 || o.Original_price == 0 {
			continue
		}

		// resembles UNIQUE constraint
		detect_duplicate_query := fmt.Sprintf(`SELECT 1 FROM _intern_view WHERE name = ? AND shop_id = %d AND volume = %4.2f AND abv = %4.2f AND original_price = %d AND discounted_price = %d AND valid_until = %d`, o.Shop, o.Volume, o.Abv, o.Original_price, o.Discounted_price, o.Valid_until)
		err := app.DB.QueryRow(detect_duplicate_query, o.Name).Scan(&found)

		if err == sql.ErrNoRows {

			_, err = stmt.Exec(o.Shop, o.Name, o.Url, o.Abv, o.Volume, o.Age, o.Original_price, o.Discounted_price, o.Base_price, o.Valid_until, o.Image_url, o.Website, o.Spirit_type, app.Now)

			if err != nil {
				o.error_msg = err.Error()
				o.error_ctx = fmt.Sprintf(`INSERT INTO angebot (shop, name, url, abv, volume, age, original_price, discounted_price, base_price, valid_until, image_url, spirit_type, created_at) VALUES (%d, "%s", "%s", %f, %f, %d, %d, %d, %d, %d, "%s", "%s", %d)`, o.Shop, o.Name, o.Url, o.Abv, o.Volume, o.Age, o.Original_price,
					o.Discounted_price, o.Base_price, o.Valid_until, o.Image_url, o.Spirit_type, app.Now)
				WarnOffer(o, fmt.Sprintf("Save Offer: Inserting offer failed (%d)", found))
			}

			DebugOffer(o, "database.go: Inserting offer.")

		} else if err != nil {
			o.error_msg = err.Error()
			o.error_ctx = fmt.Sprintf(strings.Replace(detect_duplicate_query, "?", `"%s"`, 1), o.Name)
			WarnOffer(o, "database.go: Duplicate query failed")
		} /* else {
			o.error_msg = "database.go: Duplicate detected"
			o.error_ctx = fmt.Sprintf(strings.Replace(detect_duplicate_query, "?", `"%s"`, 1), o.Name)
			DebugOffer(o, "database.go: Duplicate detected")
		}*/
	}

	//return app.remove_expired(W)
	return nil
}

func (app *App) remove_expired(W []Angebot, shop Shop) error {

	query := `SELECT id, name, shop, volume, abv, original_price, discounted_price FROM angebot WHERE shop = ? AND created_at < ?
		AND (valid_until = 0 OR valid_until > ?)`

	rows, err := app.DB.Queryx(query, shop.Id, app.Now, app.Now)
	if err != nil {
		Debug(err, "Remove expired: Query failed")
		return err
	}
	defer rows.Close()

	for rows.Next() {
		var offer_db Angebot
		err = rows.StructScan(&offer_db)

		if err != nil {
			Debug(err, "Remove expired: Struct scan failed")
			return err
		}

		if !app.offer_contains(W, offer_db) {
			DebugOffer(offer_db, "Contains not - Set to expire")
			expire_query := `UPDATE angebot SET valid_until = ? WHERE id = ?`
			_, err = app.DB.Exec(expire_query, app.Now, offer_db.Id)
			if err != nil {
				offer_db.error_msg = err.Error()
				offer_db.error_ctx = fmt.Sprintf("UPDATE angebot SET valid_until = %d WHERE id = %d", app.Now, offer_db.Id)
				WarnOffer(offer_db, "Remove expired: Update failed")
				return err
			}
		}
		DebugOffer(offer_db, "Contains! DOES NOT EXPIRE!")
	}

	return nil
}

func (app *App) offer_contains(W []Angebot, offer_db Angebot) bool {
	if len(W) < 1 {
		return false
	}

	for _, v := range W {
		// resembles UNIQUE constraint
		if (v.Name == offer_db.Name) && (v.Shop == offer_db.Shop) && (v.Volume == offer_db.Volume) && (v.Abv == offer_db.Abv) && (v.Original_price == offer_db.Original_price) && (v.Discounted_price == offer_db.Discounted_price) && (v.Valid_until == offer_db.Valid_until) {

			return true

		}
	}

	return false
}