Changeset df7f390


Ignore:
Timestamp:
01/13/25 14:18:51 (44 hours ago)
Author:
Kristijan <kristijanzafirovski26@…>
Branches:
master
Parents:
0a7426e
Message:

Added frontend functionality for changes and refactored code

Files:
2 added
13 edited

Legend:

Unmodified
Added
Removed
  • backend/GlobeGuru-backend/src/main/java/DatabaseUtil.java

    r0a7426e rdf7f390  
    5353                             "price REAL," +
    5454                             "FOREIGN KEY(optionId) REFERENCES options(id))"
     55             );
     56             PreparedStatement stmt5 = conn.prepareStatement(
     57                     "CREATE TABLE IF NOT EXISTS changeLog(" +
     58                             "id INTEGER PRIMARY KEY AUTOINCREMENT," +
     59                             "detail_id INTEGER NOT NULL," +
     60                             "attribute TEXT NOT NULL," +
     61                             "oldValue TEXT," +
     62                             "newValue TEXT," +
     63                             "FOREIGN KEY(detail_id) REFERENCES optionDetails(id))"
    5564             )) {
    5665
     
    5968            stmt3.executeUpdate();
    6069            stmt4.executeUpdate();
     70            stmt5.executeUpdate();
    6171        }
    6272    }
     
    201211        }
    202212    }
    203 
    204 
    205 
    206213    public static List<Option> poolOptionDetails(int id) throws SQLException{
    207214        String sql = "SELECT * FROM optionDetails WHERE optionId = ?";
     
    283290    }
    284291    public static int saveOptionToDatabase(Option option) {
    285         String sql = "INSERT INTO options (link, imgSrc, hotelName, country, dateRange,numberOfPeople, isPriceChanged, newPrice) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
     292        String sql = "INSERT INTO options (link, imgSrc, hotelName, country, dateRange,numberOfPeople) VALUES (?, ?, ?, ?, ?, ?)";
    286293        int id = 0;
    287294        try (Connection conn = DriverManager.getConnection("jdbc:sqlite:globe_guru.db");
     
    293300            stmt.setString(5, option.getDateRange());
    294301            stmt.setInt(6,option.getNumPeople());
    295             stmt.setBoolean(7, option.isPriceChanged());
    296             stmt.setFloat(8, option.getNewPrice());
    297302            stmt.executeUpdate();
    298303            try(ResultSet genKey = stmt.getGeneratedKeys()){
     
    331336        }
    332337    }
    333 
    334338    public static void updateOptionDetails(int detail_id, String type, String board, String amenities, float price) {
    335         String sql = "UPDATE optionDetails SET ";
    336 
    337         List<String> updates = new ArrayList<>();
    338         if (type != null && !type.isEmpty()) updates.add("type = '" + type + "'");
    339         if (board != null && !board.isEmpty()) updates.add("board = '" + board + "'");
    340         if (amenities != null && !amenities.isEmpty()) updates.add("amenities = '" + amenities + "'");
    341         updates.add("price = " + price);
    342 
    343         sql += String.join(", ", updates);
    344         sql += " WHERE id = " + detail_id;
    345 
    346         try (Connection conn = getConnection();
    347              PreparedStatement stmt = conn.prepareStatement(sql)) {
    348             stmt.executeUpdate();
     339        try (Connection conn = getConnection()) {
     340            String selectSql = "SELECT * FROM optionDetails WHERE id = ?";
     341            PreparedStatement selectStmt = conn.prepareStatement(selectSql);
     342            selectStmt.setInt(1, detail_id);
     343            ResultSet rs = selectStmt.executeQuery();
     344
     345            if (rs.next()) {
     346                String oldType = rs.getString("type");
     347                String oldBoard = rs.getString("board");
     348                String oldAmenities = rs.getString("amenities");
     349                float oldPrice = rs.getFloat("price");
     350
     351                insertChangeLog(detail_id, "type", oldType, type);
     352                insertChangeLog(detail_id, "board", oldBoard, board);
     353                insertChangeLog(detail_id, "amenities", oldAmenities, amenities);
     354                insertChangeLog(detail_id, "price", String.valueOf(oldPrice), String.valueOf(price));
     355            }
     356
     357            String sql = "UPDATE optionDetails SET ";
     358            List<String> updates = new ArrayList<>();
     359            if (type != null && !type.isEmpty()) updates.add("type = '" + type + "'");
     360            if (board != null && !board.isEmpty()) updates.add("board = '" + board + "'");
     361            if (amenities != null && !amenities.isEmpty()) updates.add("amenities = '" + amenities + "'");
     362            if (price > 0) updates.add("price = " + price);
     363
     364            sql += String.join(", ", updates);
     365            sql += " WHERE id = " + detail_id;
     366
     367            try (PreparedStatement stmt = conn.prepareStatement(sql)) {
     368                stmt.executeUpdate();
     369            }
    349370        } catch (SQLException e) {
    350371            e.printStackTrace();
    351372        }
    352373    }
    353 
    354     public static void updateOptionInDatabase(Option option) {
    355         String sql = "UPDATE options SET link = ?, imgSrc = ?, hotelName = ?, country = ?, price = ?, dateRange = ?, isPriceChanged = ?, newPrice = ? WHERE id = ?";
    356         try (Connection conn = DriverManager.getConnection("jdbc:sqlite:globe_guru.db");
    357              PreparedStatement stmt = conn.prepareStatement(sql)) {
    358             stmt.setString(1, option.getLink());
    359             stmt.setString(2, option.getImgSrc());
    360             stmt.setString(3, option.getHotelName());
    361             stmt.setString(4, option.getCountry());
    362             stmt.setFloat(5, option.getPrice());
    363             stmt.setString(6, option.getDateRange());
    364             stmt.setBoolean(7, option.isPriceChanged());
    365             stmt.setFloat(8, option.getNewPrice());
    366             stmt.setInt(9, option.getId());
    367             stmt.executeUpdate();
    368         } catch (SQLException e) {
    369             e.printStackTrace();
    370         }
    371     }
    372     public static Option findOption(Option option) {
    373         String sql = "SELECT * FROM options WHERE id = ?";
    374         try (Connection conn = DriverManager.getConnection("jdbc:sqlite:globe_guru.db");
    375              PreparedStatement stmt = conn.prepareStatement(sql)) {
    376             stmt.setInt(1, option.getId());
    377             try (ResultSet rs = stmt.executeQuery()) {
    378                 if (rs.next()) {
    379                     Option existingOption = new Option();
    380                     existingOption.setId(rs.getInt("id"));
    381                     existingOption.setLink(rs.getString("link"));
    382                     existingOption.setImgSrc(rs.getString("imgSrc"));
    383                     existingOption.setHotelName(rs.getString("hotelName"));
    384                     existingOption.setCountry(rs.getString("country"));
    385                     existingOption.setPrice(rs.getFloat("price"));
    386                     existingOption.setDateRange(rs.getString("dateRange"));
    387                     existingOption.setPriceChanged(rs.getBoolean("isPriceChanged"));
    388                     existingOption.setNewPrice(rs.getInt("newPrice"));
    389                     existingOption.setNumPeople(rs.getInt("numberOfPeople"));
    390                     return existingOption;
    391                 }
    392             }
    393         } catch (SQLException e) {
    394             e.printStackTrace();
    395         }
    396         return null;
    397     }
    398 
    399 
     374    private static void insertChangeLog(int detail_id, String attribute, String oldValue, String newValue) {
     375        if (!oldValue.equals(newValue)) {
     376            String sql = "INSERT INTO changeLog (detail_id, attribute, oldValue, newValue) VALUES (?, ?, ?, ?)";
     377            try (Connection conn = getConnection();
     378                 PreparedStatement stmt = conn.prepareStatement(sql)) {
     379                stmt.setInt(1, detail_id);
     380                stmt.setString(2, attribute);
     381                stmt.setString(3, oldValue);
     382                stmt.setString(4, newValue);
     383                stmt.executeUpdate();
     384            } catch (SQLException e) {
     385                e.printStackTrace();
     386            }
     387        }
     388    }
    400389    public static int getCurrentOptionsCount() throws SQLException {
    401390        String sql = "SELECT COUNT(*) AS optionsCount FROM options";
  • backend/GlobeGuru-backend/src/main/java/Option.java

    r0a7426e rdf7f390  
     1import java.util.ArrayList;
     2import java.util.List;
    13import java.util.Objects;
    24
     
    1214    private String board;
    1315    private String amenities;
     16    private int numPeople;
     17    private String dateRange;
     18    private List<Change> changes = new ArrayList<>();
    1419
    15     private int numPeople;
    16     //Price changing
    17     private float newPrice = 0;
    18     private boolean isPriceChanged = false;
    19     private String dateRange;
    20     // Constructor
    21     public Option(){
    22         price = 0;
     20    public String getDateRange() {
     21        return dateRange;
    2322    }
    2423
     
    3130    }
    3231
     32    public int getId() {
     33        return id;
     34    }
     35
     36    public void setId(int id) {
     37        this.id = id;
     38    }
     39
    3340    public void setDetail_id(int detail_id) {
    3441        this.detail_id = detail_id;
    35     }
    36 
    37     public String getDateRange() {
    38         return dateRange;
    3942    }
    4043
     
    6366    }
    6467
    65     public boolean isEmpty(){
    66         return (hotelName == null || country == null || link == null || imgSrc == null);
     68    public int getNumPeople() {
     69        return numPeople;
    6770    }
     71
     72    public void setNumPeople(int numPeople) {
     73        this.numPeople = numPeople;
     74    }
     75
    6876    public String getHotelName() {
    6977        return hotelName;
     
    98106    }
    99107
     108    public String getImgSrc() {
     109        return imgSrc;
     110    }
     111
    100112    public void setImgSrc(String imgSrc) {
    101113        this.imgSrc = imgSrc;
    102114    }
    103115
    104     public String getImgSrc() {
    105         return imgSrc;
     116    public void addChange(String attribute, String oldValue, String newValue) {
     117        this.changes.add(new Change(attribute, oldValue, newValue));
    106118    }
    107119
    108     public int getNumPeople() {
    109         return numPeople;
    110     }
    111 
    112     public void setNumPeople(int numPeople) {
    113         this.numPeople = numPeople;
     120    public List<Change> getChanges() {
     121        return changes;
    114122    }
    115123
    116124    @Override
    117125    public boolean equals(Object obj) {
    118         if(this==obj) return true;
    119         if(obj == null || getClass() != obj.getClass()) return false;
     126        if (this == obj) return true;
     127        if (obj == null || getClass() != obj.getClass()) return false;
    120128        Option option = (Option) obj;
    121129        return Float.compare(option.price, price) == 0
     
    127135    @Override
    128136    public int hashCode() {
    129         return Objects.hash(hotelName,country,price,link);
     137        return Objects.hash(hotelName, country, price, link);
    130138    }
    131139
    132     public int getId() {
    133         return id;
    134     }
    135 
    136     public void setId(int id) {
    137         this.id = id;
    138     }
    139 
    140     //debug
    141140    @Override
    142141    public String toString() {
    143142        return "Option{" +
    144                 "id='" + id + '\'' +
    145                 "dateRange='" + dateRange + '\'' +
    146                 "hotelName='" + hotelName + '\'' +
     143                "id=" + id +
     144                ", dateRange='" + dateRange + '\'' +
     145                ", hotelName='" + hotelName + '\'' +
    147146                ", country='" + country + '\'' +
    148                 ", price='" + price + '\'' +
     147                ", price=" + price +
    149148                ", link='" + link + '\'' +
    150                 ", image='" + imgSrc +
     149                ", imgSrc='" + imgSrc + '\'' +
     150                ", type='" + type + '\'' +
     151                ", board='" + board + '\'' +
     152                ", amenities='" + amenities + '\'' +
     153                ", numPeople=" + numPeople +
     154                ", changes=" + changes +
    151155                '}';
    152156    }
    153157
    154     public void setPriceChanged(boolean a){
    155         isPriceChanged = a;
    156     }
    157     public void setNewPrice(float a){
    158         newPrice = a;
    159     }
    160 
    161     public boolean isPriceChanged() {
    162         return isPriceChanged;
    163     }
    164 
    165     public float getNewPrice() {
    166         return newPrice;
     158    public boolean isEmpty() {
     159        return (link.isEmpty() || country.isEmpty() || hotelName.isEmpty());
    167160    }
    168161}
     162
  • backend/GlobeGuru-backend/src/main/java/Scraper.java

    r0a7426e rdf7f390  
    1414
    1515    private List<String> urls;
    16     private ConcurrentLinkedQueue<Option> optionsQueue;
    1716    private CountDownLatch latch;
    1817
    1918    public Scraper() {
    2019        urls = new ArrayList<>();
    21         this.optionsQueue = new ConcurrentLinkedQueue<>();
    2220        ObjectMapper mapper = new ObjectMapper();
    2321        try {
     
    4442        System.out.println("Scraper has started ");
    4543        for (String url : urls) {
    46             new ScraperThread(url, optionsQueue, latch).start();
     44            new ScraperThread(url, latch).start();
    4745        }
    4846        return null;
  • backend/GlobeGuru-backend/src/main/java/ScraperThread.java

    r0a7426e rdf7f390  
    2929public class ScraperThread extends Thread {
    3030    private String url;
    31     private ConcurrentLinkedQueue<Option> uniqueOptions;
    3231    private CountDownLatch latch;
    33     private Set<Option> optionSet;
    34 
    35     public ScraperThread(String url, ConcurrentLinkedQueue<Option> optionsQueue, CountDownLatch latch) {
     32
     33    public ScraperThread(String url, CountDownLatch latch) {
    3634        this.url = url;
    37         this.uniqueOptions = optionsQueue;
    3835        this.latch = latch;
    39         this.optionSet = new HashSet<>();
    4036    }
    4137
     
    8682                    for (Element div : childDivs) {
    8783                        String data = div.outerHtml();
    88                         Option option = optionParser(data,numPeople);
     84                        Option option = optionParser(data, numPeople);
    8985                        if (option != null) {
    90                             Option existingOption = DatabaseUtil.findOption(option);
    91                             if (existingOption != null) {
    92                                 if (existingOption.equals(option)) {
    93                                     option.setPriceChanged(true);
    94                                     option.setNewPrice(option.getPrice());
    95                                 }
    96                                 DatabaseUtil.updateOptionInDatabase(option);
    97                             } else if (optionSet.add(option)) {
    98                                 uniqueOptions.add(option);
    99                                 option.setId(DatabaseUtil.saveOptionToDatabase(option));
    100                                 scrapeOptionInfo(option);
    101                                 System.out.println("Parsed " + option);
    102                             }
     86                            option.setId(DatabaseUtil.saveOptionToDatabase(option));
     87                            scrapeOptionInfo(option);
     88                            System.out.println("Parsed " + option);
    10389                        }
    10490                    }
     
    115101                    for (Element div : childDivs) {
    116102                        String data = div.outerHtml();
    117                         Option newOption = optionParser(data,numPeople);
     103                        Option newOption = optionParser(data, numPeople);
    118104                        if (newOption != null) {
    119                             if (optionSet.add(newOption)) {
    120                                 uniqueOptions.add(newOption);
    121 
    122                                 newOption.setId(DatabaseUtil.saveOptionToDatabase(newOption));
    123                                 scrapeOptionInfo(newOption);
    124                                 System.out.println("Parsed " + newOption);
    125                             }
    126                         }
    127                     }
    128 
    129         } else {
     105                            newOption.setId(DatabaseUtil.saveOptionToDatabase(newOption));
     106                            scrapeOptionInfo(newOption);
     107                            System.out.println("Parsed " + newOption);
     108                        }
     109                    }
     110                } else {
    130111                    System.out.println("Parent div not found");
    131112                }
     
    134115                System.out.println("URL not recognized for parsing.");
    135116        }
     117
    136118    }
    137119    private void scrapeOptionInfo(Option option) {
  • backend/GlobeGuru-backend/target/classes/lastUpdateTime.json

    r0a7426e rdf7f390  
    11{
    2   "lastUpdateTime" : "2025-01-11T00:07:21.694581100"
     2  "lastUpdateTime" : "2025-01-13T13:53:28.872595600"
    33}
  • frontend/index.html

    r0a7426e rdf7f390  
    5454        <span class="close">&times;</span>
    5555        <div class="saved-trips-container">
    56             <h2>Saved Trips</h2>
     56            <h2>Зачувани патувања</h2>
    5757            <div id="savedTripsList"></div>
    5858        </div>
    5959        <div class="price-changes-container">
    60             <h2>Price Changes</h2>
     60            <h2>Промени во понудата</h2>
    6161            <div id="priceChangesList"></div>
    6262        </div>
     
    107107    <div class="loading-content">
    108108        <div class="spinner"></div>
    109         <p>Loading, please wait...</p>
     109        <p>Пребарувам. ве молиме почекајте...</p>
    110110    </div>
    111111</div>
    112112<div id="AccountDetailsOverlay" class="popup accDetails">
    113113    <div class="popup-content">
    114         <h1>My account</h1>
     114        <h1>Мој профил</h1>
    115115        <div class="popup-content" style="display: none" id="confDel">
    116116            <div class="delete" >
    117                 <h1>Are you sure you want to delete your account?</h1>
    118                 <button onclick="deleteAccount()" class="button" id="confirmDelete" style="background-color: red">Yes</button>
    119                 <button id="cancel" class="button" onclick="closeConfirmDelete()">No</button>
     117                <h1>Дали сте сигурни дека сакате да го избришете профилот?</h1>
     118                <button onclick="deleteAccount()" class="button" id="confirmDelete" style="background-color: red">Да</button>
     119                <button id="cancel" class="button" onclick="closeConfirmDelete()">Не</button>
    120120            </div>
    121121        </div>
    122         <button class="button" id="pswdChange">Change password</button>
    123         <button class="button" style="background-color: red" id="deleteAcc" onclick="confirmDelete()">Delete Account</button>
     122        <button class="button" style="background-color: red" id="deleteAcc" onclick="confirmDelete()">Избриши профил</button>
    124123    </div>
    125124</div>
  • frontend/js/formHandler.js

    r0a7426e rdf7f390  
    5151                const dataList = document.getElementById('givenOptions');
    5252                dataList.innerHTML = '';
     53
    5354                if (data.length === 0) {
    5455                    const wrapper = document.createElement('div');
     
    6667                        img.src = item.imgSrc || '';
    6768                        optionDiv.appendChild(img);
    68                         const WrapperDiv = document.createElement('div');
    69                         optionDiv.appendChild(WrapperDiv);
     69                        const wrapperDiv = document.createElement('div');
     70                        optionDiv.appendChild(wrapperDiv);
    7071
    7172                        const nameParagraph = document.createElement('p');
    7273                        nameParagraph.id = 'name';
    7374                        nameParagraph.textContent = item.hotelName || 'N/A';
    74                         WrapperDiv.appendChild(nameParagraph);
     75                        wrapperDiv.appendChild(nameParagraph);
    7576
    7677                        const countryParagraph = document.createElement('p');
     
    7879                        countryParagraph.style.fontSize = '20px';
    7980                        countryParagraph.textContent = item.country || 'N/A';
    80                         WrapperDiv.appendChild(countryParagraph);
     81                        wrapperDiv.appendChild(countryParagraph);
    8182
    8283                        const dateParagraph = document.createElement('h2');
    8384                        dateParagraph.id = 'date';
    8485                        dateParagraph.textContent = item.dateRange || 'N/A';
    85                         WrapperDiv.appendChild(dateParagraph);
     86                        wrapperDiv.appendChild(dateParagraph);
    8687
    8788                        const peopleParagraph = document.createElement('p');
    8889                        peopleParagraph.id = 'numPeople';
    8990                        peopleParagraph.textContent = item.numPeople === 1 ? item.numPeople + " лице" : item.numPeople + " лица";
    90                         WrapperDiv.appendChild(peopleParagraph);
     91                        wrapperDiv.appendChild(peopleParagraph);
    9192
    9293                        const priceHeading = document.createElement('h1');
    9394                        priceHeading.textContent = 'Цена:';
    94                         WrapperDiv.appendChild(priceHeading);
     95                        wrapperDiv.appendChild(priceHeading);
    9596                        const priceParagraph = document.createElement('h2');
    9697                        priceParagraph.id = 'price';
    9798                        priceParagraph.textContent = (item.price || 0) + " EUR";
    98                         WrapperDiv.appendChild(priceParagraph);
     99                        wrapperDiv.appendChild(priceParagraph);
    99100
    100101                        const infoDiv = document.createElement('div');
     
    110111                        infoDiv.appendChild(amenities);
    111112                        optionDiv.appendChild(infoDiv);
     113
     114                        const changesDiv = document.createElement('div');
     115                        item.changes.forEach(change => {
     116                            const changeParagraph = document.createElement('p');
     117                            changeParagraph.textContent = change.attribute + " changed from " + change.oldValue + " to " + change.newValue;
     118                            changesDiv.appendChild(changeParagraph);
     119                        });
     120                        optionDiv.appendChild(changesDiv);
    112121
    113122                        const link = document.createElement('a');
  • frontend/js/siteFlow.js

    r0a7426e rdf7f390  
    168168        .then(data => {
    169169            console.log('Option saved:', data);
    170             // Update the UI to reflect that the option was saved
    171170            updateFavoriteButtons();
    172171        })
     
    243242                tripDiv.appendChild(price);
    244243
     244                // Add change indicator
     245                if(trip.changes && trip.changes.length > 0) {
     246                    const changeDiv = document.createElement('div');
     247                    changeDiv.classList.add('changes');
     248                    trip.changes.forEach(change => {
     249                        const changeParagraph = document.createElement('p');
     250                        changeParagraph.textContent = `${change.attribute} се смени од ${change.oldValue} во ${change.newValue}`;
     251                        changeDiv.appendChild(changeParagraph);
     252                    });
     253                    tripDiv.appendChild(changeDiv);
     254                }
     255
    245256                savedTripsList.appendChild(tripDiv);
    246257            });
    247 
    248258
    249259            const priceChangesList = document.getElementById('priceChangesList');
     
    258268                });
    259269            } else {
    260                 priceChangesList.textContent = "нема промена на цената.";
     270                priceChangesList.textContent = "нема промена во зачуваните патувања.";
    261271            }
    262272        })
     
    265275        });
    266276}
     277
    267278function removeFromSaved(optionId) {
    268279    const userEmail = sessionStorage.getItem('user').replace(/^"|"$/g, '');
Note: See TracChangeset for help on using the changeset viewer.