diff --git a/build.gradle b/build.gradle index 36ec6d8..64064f3 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ allprojects { apply plugin: 'java' - version = '0.1.2' + version = '0.1.3' group = 'net.hobbyscience.housedb' repositories { diff --git a/database/src/main/java/db/migration/units/R__unit_conversions.java b/database/src/main/java/db/migration/java/units/R__unit_conversions.java similarity index 99% rename from database/src/main/java/db/migration/units/R__unit_conversions.java rename to database/src/main/java/db/migration/java/units/R__unit_conversions.java index 46f2838..530adfb 100644 --- a/database/src/main/java/db/migration/units/R__unit_conversions.java +++ b/database/src/main/java/db/migration/java/units/R__unit_conversions.java @@ -1,4 +1,4 @@ -package db.migration.units; +package db.migration.java.units; import java.io.BufferedReader; import java.io.InputStream; diff --git a/database/src/main/resources/db/migration/Locations/R__a_locations.sql b/database/src/main/resources/db/migration/Locations/R__a_locations.sql index d492f3f..4eac9c6 100644 --- a/database/src/main/resources/db/migration/Locations/R__a_locations.sql +++ b/database/src/main/resources/db/migration/Locations/R__a_locations.sql @@ -50,10 +50,19 @@ BEGIN foreach cur_level in ARRAY parts loop -- search for existing object at this level - --raise info 'Search for (%,%)', cur_level,the_parent_id; - select into the_id,found_parent_id id,parent_id from locations where name=cur_level and (parent_id = the_parent_id); -- or parent_id is null); + raise info 'Search for (%,%)', cur_level,the_parent_id; + select + into the_id,found_parent_id id,parent_id + from locations + where + lower(name)=lower(cur_level) + and ( + (parent_id = the_parent_id) + or + (parent_id is null and the_parent_id is null) + ); if the_id is NULL THEN - --raise notice 'insert new value'; + raise notice 'insert new value'; --raise notice '%',found_parent_id ; insert into locations(name,parent_id) values (cur_level,the_parent_id) returning id into the_id; the_parent_id = the_id; @@ -65,4 +74,51 @@ BEGIN end loop; RETURN the_id; END; -$$ LANGUAGE plpgsql; \ No newline at end of file +$$ LANGUAGE plpgsql; + + +create or replace function housedb_timeseries.insert_location() +returns trigger +as $$ +declare + loc_info housedb.timeseries%rowtype; + l_loc_id bigint; + l_parent_id bigint; + loc_name text; +begin + set search_path to housedb_locations,housedb_units,housedb,public; + if TG_OP = 'DELETE' then + raise notice 'deleting %', OLD; + return OLD; + else + --raise notice 'Inserting or updating value %', NEW; + if NEW.id is not null and NEW.name is not null THEN + raise exception 'Specify only timeseries_id or name, not both' using ERRCODE = 'ZX082'; + end if; + + if TG_OP = 'UPDATE' then + perform 'housedb_security.can_perform(housedb_security.get_session_user(),''UPDATE'',''timeseries'',ts_name)'; + raise exception 'Update not supported at this time'; + else -- INSERT + perform 'housedb_security.can_perform(housedb_security.get_session_user(),''CREATE'',''timeseries'',ts_name)'; + select housedb_locations.create_location(NEW.name) into l_loc_id; + select into l_parent_id parent_id from housedb.view_locations where lower(name) = lower(NEW.name); + NEW.id = l_loc_id; + NEW.parent_id = l_parent_id; + update locations + set latitude = NEW.latitude, + longitude = NEW.longitude, + horizontal_datum = NEW.horizontal_datum, + elevation = NEW.elevation, + vertical_datum = NEW.vertical_datum + where id = NEW.id; + end if ; + + + end if; + + return new; +end; +$$ language plpgsql; + + diff --git a/database/src/main/resources/db/migration/V4/V4.5.1__fix_location_uniqueness.sql b/database/src/main/resources/db/migration/V4/V4.5.1__fix_location_uniqueness.sql new file mode 100644 index 0000000..22af342 --- /dev/null +++ b/database/src/main/resources/db/migration/V4/V4.5.1__fix_location_uniqueness.sql @@ -0,0 +1,4 @@ +drop index housedb.location_names_lower; + +create unique index location_names_lower_no_parent on housedb.locations( lower(name) ) where parent_id is null; +create unique index location_names_lower_has_parent on housedb.locations( lower(name), parent_id ) where parent_id is not null; \ No newline at end of file diff --git a/database/src/main/resources/db/migration/V5/V5.0.0__location_coordinates.sql b/database/src/main/resources/db/migration/V5/V5.0.0__location_coordinates.sql new file mode 100644 index 0000000..21f8bda --- /dev/null +++ b/database/src/main/resources/db/migration/V5/V5.0.0__location_coordinates.sql @@ -0,0 +1,5 @@ +alter table housedb.locations add column latitude double precision; +alter table housedb.locations add column longitude double precision; +alter table housedb.locations add column horizontal_datum varchar(50); +alter table housedb.locations add column elevation double precision; +alter table housedb.locations add column vertical_datum varchar(50); \ No newline at end of file diff --git a/database/src/main/resources/db/migration/Views/R__locations.sql b/database/src/main/resources/db/migration/Views/R__locations.sql new file mode 100644 index 0000000..39c8772 --- /dev/null +++ b/database/src/main/resources/db/migration/Views/R__locations.sql @@ -0,0 +1,20 @@ +create or replace view housedb.view_locations as + select id, + housedb_locations.expand_location_name( + id + ) AS name, + parent_id, + housedb_locations.expand_location_name( + parent_id + ) AS parent, + latitude, + longitude, + horizontal_datum, + elevation, + vertical_datum + from housedb.locations +; + +drop trigger if exists insert_location_trigger on housedb.view_locations; +create trigger insert_location_trigger instead of insert or update or delete on housedb.view_locations + for each row execute procedure housedb_timeseries.insert_location(); diff --git a/database/src/test/java/db/migration/UnitConversionTest.java b/database/src/test/java/db/migration/UnitConversionTest.java index 2eda3fc..9e2e39e 100644 --- a/database/src/test/java/db/migration/UnitConversionTest.java +++ b/database/src/test/java/db/migration/UnitConversionTest.java @@ -2,7 +2,7 @@ import org.junit.jupiter.api.Test; -import db.migration.units.R__unit_conversions; +import db.migration.java.units.R__unit_conversions; import net.hobbyscience.SimpleInfixCalculator; import net.hobbyscience.database.Conversion; import net.hobbyscience.database.Unit; diff --git a/database/src/test/sql/location_test.sql b/database/src/test/sql/location_test.sql index db11e01..a067ec2 100644 --- a/database/src/test/sql/location_test.sql +++ b/database/src/test/sql/location_test.sql @@ -57,6 +57,43 @@ END; $$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION housedb_tests.test_create_with_sub_doesnt_create_duplicate() +RETURNS SETOF TEXT AS $$ +DECLARE + base_name text = 'Test'; + full_loc text = 'Test-Sub Location'; + n_rows int; +BEGIN + perform housedb_security.add_permission('guest', 'CREATE', 'locations','.*'); + + perform housedb_locations.create_location(base_name); + select into n_rows count(*) from housedb.locations; + RETURN NEXT ok( n_rows = 1, 'Should only have 1 row'); + + perform housedb_locations.create_location(full_loc); + select into n_rows count(*) from housedb.locations; + RETURN NEXT ok( n_rows = 2, 'Should only have 2 rows, duplicate row created'); + +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION housedb_tests.test_insert_new_locations_into_view() +RETURNS SETOF TEXT AS $$ +DECLARE + n_rows int; +BEGIN + perform housedb_security.add_permission('guest', 'CREATE', 'locations','.*'); + + insert into + housedb.view_locations(name,latitude,longitude,horizontal_datum) + values ('Test',0,3,'RASTER'); + select into n_rows count(*) from housedb.locations; + RETURN NEXT ok( n_rows = 1, 'There should be one row'); + +END; +$$ LANGUAGE plpgsql; + + diff --git a/housedb-dao/.classpath b/housedb-dao/.classpath deleted file mode 100644 index 5785087..0000000 --- a/housedb-dao/.classpath +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/housedb-dao/src/main/java/net/hobbyscience/housedb/dao/Dao.java b/housedb-dao/src/main/java/net/hobbyscience/housedb/dao/Dao.java new file mode 100644 index 0000000..21c7b73 --- /dev/null +++ b/housedb-dao/src/main/java/net/hobbyscience/housedb/dao/Dao.java @@ -0,0 +1,23 @@ +package net.hobbyscience.housedb.dao; + +import java.util.List; +import java.util.Optional; + +import org.jooq.DSLContext; + +public abstract class Dao { + + @SuppressWarnings("unused") + protected DSLContext dsl = null; + + public Dao(DSLContext dsl){ + this.dsl = dsl; + } + + public abstract List getAll(); + public abstract Optional getByUniqueName(String uniqueName); + + public abstract void update(T modified); + public abstract void save(T newObj); + +} diff --git a/housedb-dao/src/main/java/net/hobbyscience/housedb/dao/HouseDb.java b/housedb-dao/src/main/java/net/hobbyscience/housedb/dao/HouseDb.java index 3fa6bb1..4c7ec1a 100644 --- a/housedb-dao/src/main/java/net/hobbyscience/housedb/dao/HouseDb.java +++ b/housedb-dao/src/main/java/net/hobbyscience/housedb/dao/HouseDb.java @@ -9,6 +9,9 @@ import java.util.ArrayList; import static net.hobbyscience.housedb.housedb.Routines.*; + +import net.hobbyscience.housedb.dto.Location; +import net.hobbyscience.housedb.dto.TimeSeries; import net.hobbyscience.housedb.housedb.tables.*; import static net.hobbyscience.housedb.housedb.tables.TimeseriesValues.*; @@ -17,6 +20,7 @@ import static net.hobbyscience.housedb.housedb.tables.Parameters.*; import static net.hobbyscience.housedb.housedb.tables.Intervals.*; import net.hobbyscience.housedb.housedb_security.Routines; +import net.hobbyscience.housedb.types.DataTriple; import static org.jooq.impl.DSL.*; @@ -53,6 +57,10 @@ public HouseDb setUsername(String username){ return this; } + public LocationsDao locationDao(){ + return new LocationsDao(dsl); + } + public List getAllLocations() throws Exception{ ArrayList locations = new ArrayList<>(); diff --git a/housedb-dao/src/main/java/net/hobbyscience/housedb/dao/Location.java b/housedb-dao/src/main/java/net/hobbyscience/housedb/dao/Location.java deleted file mode 100644 index b1f11d4..0000000 --- a/housedb-dao/src/main/java/net/hobbyscience/housedb/dao/Location.java +++ /dev/null @@ -1,50 +0,0 @@ -package net.hobbyscience.housedb.dao; - -import java.util.Objects; - -public class Location{ - private String name; - - public Location() { - } - - public Location(String name) { - this.name = name; - } - - public String getName() { - return this.name; - } - - public void setName(String name) { - this.name = name; - } - - public Location name(String name) { - setName(name); - return this; - } - - @Override - public boolean equals(Object o) { - if (o == this) - return true; - if (!(o instanceof Location)) { - return false; - } - Location location = (Location) o; - return Objects.equals(name, location.name); - } - - @Override - public int hashCode() { - return Objects.hashCode(name); - } - - @Override - public String toString() { - return "{" + - " name='" + getName() + "'" + - "}"; - } -} \ No newline at end of file diff --git a/housedb-dao/src/main/java/net/hobbyscience/housedb/dao/LocationsDao.java b/housedb-dao/src/main/java/net/hobbyscience/housedb/dao/LocationsDao.java new file mode 100644 index 0000000..97ea8e1 --- /dev/null +++ b/housedb-dao/src/main/java/net/hobbyscience/housedb/dao/LocationsDao.java @@ -0,0 +1,108 @@ +package net.hobbyscience.housedb.dao; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.jooq.DSLContext; +import org.jooq.impl.DSL; + +import net.hobbyscience.housedb.dto.Location; + +import static net.hobbyscience.housedb.housedb.tables.ViewLocations.*; +import static net.hobbyscience.housedb.housedb.tables.Locations.*; + +public class LocationsDao extends Dao{ + + public LocationsDao(DSLContext dsl) { + super(dsl); + } + + @Override + public List getAll() { + var locs = dsl + .select() + .from(VIEW_LOCATIONS).orderBy(VIEW_LOCATIONS.NAME) + .fetch().stream().map( r -> r.into(VIEW_LOCATIONS) ) + .map( r -> { + return new Location ( + r.getId(), + r.getName(), + r.getParent(), + r.getLatitude(), + r.getLongitude(), + r.getHorizontalDatum(), + r.getElevation(), + r.getVerticalDatum() + ); + }).collect(Collectors.toList()); + return locs; + } + + @Override + public Optional getByUniqueName(String uniqueName) { + var res = dsl + .select() + .from(VIEW_LOCATIONS) + .where(DSL.upper(VIEW_LOCATIONS.NAME).eq(DSL.upper(uniqueName))) + .orderBy(VIEW_LOCATIONS.NAME) + .fetchOne(); + if( res == null ){ + return Optional.ofNullable(null); + } else { + var r = res.into(VIEW_LOCATIONS); + return Optional.of( + new Location ( + r.getId(), + r.getName(), + r.getParent(), + r.getLatitude(), + r.getLongitude(), + r.getHorizontalDatum(), + r.getElevation(), + r.getVerticalDatum() + ) + ); + } + + + } + + @Override + public void update(Location modified) { + dsl.update(VIEW_LOCATIONS) + .set(VIEW_LOCATIONS.NAME,modified.getName()) + + .set(VIEW_LOCATIONS.LATITUDE,modified.getLatitude()) + .set(VIEW_LOCATIONS.LONGITUDE,modified.getLongitude()) + .set(VIEW_LOCATIONS.HORIZONTAL_DATUM,modified.getHorizontalDatum()) + + .set(VIEW_LOCATIONS.LATITUDE,modified.getElevation()) + .set(VIEW_LOCATIONS.VERTICAL_DATUM,modified.getVerticalDatum()) + + .where(VIEW_LOCATIONS.ID.eq(modified.getId())) + .execute(); + } + + @Override + public void save(Location newObj) { + dsl.insertInto(VIEW_LOCATIONS) + .columns( + VIEW_LOCATIONS.NAME, + VIEW_LOCATIONS.LATITUDE, + VIEW_LOCATIONS.LONGITUDE, + VIEW_LOCATIONS.HORIZONTAL_DATUM, + VIEW_LOCATIONS.ELEVATION, + VIEW_LOCATIONS.VERTICAL_DATUM + ).values( + newObj.getName(), + newObj.getLatitude(), + newObj.getLongitude(), + newObj.getHorizontalDatum(), + newObj.getElevation(), + newObj.getVerticalDatum() + ).execute(); + + } + +} diff --git a/housedb-dao/src/main/java/net/hobbyscience/housedb/dto/Location.java b/housedb-dao/src/main/java/net/hobbyscience/housedb/dto/Location.java new file mode 100644 index 0000000..a4fe4d1 --- /dev/null +++ b/housedb-dao/src/main/java/net/hobbyscience/housedb/dto/Location.java @@ -0,0 +1,122 @@ +package net.hobbyscience.housedb.dto; + +import java.util.Objects; + +import com.fasterxml.jackson.annotation.*; + + +public class Location{ + @JsonIgnore + private long id; + private String name; + @JsonIgnore + private String parent; + private Double latitude; + private Double longitude; + private String horizontalDatum; + private Double elevation; + private String verticalDatum; + + + @JsonCreator + public Location( + @JsonProperty("name") String name, + @JsonProperty("latitude") Double latitude, + @JsonProperty("longitude") Double longitude, + @JsonProperty("horizontalDatum") String horizontalDatum, + @JsonProperty("elevation") Double elevation, + @JsonProperty("verticalDatum") String verticalDatum) { + this.id = -1; + this.name = name; + this.parent = null; + this.latitude = latitude; + this.longitude = longitude; + this.horizontalDatum = horizontalDatum; + this.elevation = elevation; + this.verticalDatum = verticalDatum; + } + + public Location( + long id, + String name, + String parent, + Double latitude, + Double longitude, + String horizontalDatum, + Double elevation, + String verticalDatum) { + this.id = id; + this.name = name; + this.parent = parent; + this.latitude = latitude; + this.longitude = longitude; + this.horizontalDatum = horizontalDatum; + this.elevation = elevation; + this.verticalDatum = verticalDatum; + } + + @JsonIgnore + public long getId(){ + return this.id; + } + + @JsonIgnore + public String getParent() { + return this.parent; + } + + public Double getLatitude() { + return this.latitude; + } + + public Double getLongitude() { + return this.longitude; + } + + public String getHorizontalDatum() { + return this.horizontalDatum; + } + + public Double getElevation() { + return this.elevation; + } + + public String getVerticalDatum() { + return this.verticalDatum; + } + + public String getName() { + return this.name; + } + + + @Override + public boolean equals(Object o) { + if (o == this) + return true; + if (!(o instanceof Location)) { + return false; + } + Location location = (Location) o; + return Objects.equals(name, location.name) && Objects.equals(parent, location.parent) && Objects.equals(latitude, location.latitude) && Objects.equals(longitude, location.longitude) && Objects.equals(horizontalDatum, location.horizontalDatum) && Objects.equals(elevation, location.elevation) && Objects.equals(verticalDatum, location.verticalDatum); + } + + @Override + public int hashCode() { + return Objects.hash(name, parent, latitude, longitude, horizontalDatum, elevation, verticalDatum); + } + + + @Override + public String toString() { + return "{" + + " name='" + name + "'" + + ", parent='" + parent + "'" + + ", latitude='" + latitude + "'" + + ", longitude='" + longitude + "'" + + ", horizontal_datum='" + horizontalDatum + "'" + + ", elevation='" + elevation + "'" + + ", vertical_datum='" + verticalDatum + "'" + + "}"; + } +} \ No newline at end of file diff --git a/housedb-dao/src/main/java/net/hobbyscience/housedb/dao/TimeSeries.java b/housedb-dao/src/main/java/net/hobbyscience/housedb/dto/TimeSeries.java similarity index 95% rename from housedb-dao/src/main/java/net/hobbyscience/housedb/dao/TimeSeries.java rename to housedb-dao/src/main/java/net/hobbyscience/housedb/dto/TimeSeries.java index 70ffc1d..18e95f3 100644 --- a/housedb-dao/src/main/java/net/hobbyscience/housedb/dao/TimeSeries.java +++ b/housedb-dao/src/main/java/net/hobbyscience/housedb/dto/TimeSeries.java @@ -1,4 +1,4 @@ -package net.hobbyscience.housedb.dao; +package net.hobbyscience.housedb.dto; import java.util.List; @@ -6,6 +6,7 @@ import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Schema; +import net.hobbyscience.housedb.types.DataTriple; /** * */ diff --git a/housedb-dao/src/main/java/net/hobbyscience/housedb/dao/DataTriple.java b/housedb-dao/src/main/java/net/hobbyscience/housedb/types/DataTriple.java similarity index 97% rename from housedb-dao/src/main/java/net/hobbyscience/housedb/dao/DataTriple.java rename to housedb-dao/src/main/java/net/hobbyscience/housedb/types/DataTriple.java index c754465..b1d0b6a 100644 --- a/housedb-dao/src/main/java/net/hobbyscience/housedb/dao/DataTriple.java +++ b/housedb-dao/src/main/java/net/hobbyscience/housedb/types/DataTriple.java @@ -1,4 +1,4 @@ -package net.hobbyscience.housedb.dao; +package net.hobbyscience.housedb.types; import java.time.OffsetDateTime; diff --git a/housedb-dao/src/main/java/net/hobbyscience/housedb/dao/DataTripleDeserializer.java b/housedb-dao/src/main/java/net/hobbyscience/housedb/types/serializers/DataTripleDeserializer.java similarity index 91% rename from housedb-dao/src/main/java/net/hobbyscience/housedb/dao/DataTripleDeserializer.java rename to housedb-dao/src/main/java/net/hobbyscience/housedb/types/serializers/DataTripleDeserializer.java index b27b115..003bf98 100644 --- a/housedb-dao/src/main/java/net/hobbyscience/housedb/dao/DataTripleDeserializer.java +++ b/housedb-dao/src/main/java/net/hobbyscience/housedb/types/serializers/DataTripleDeserializer.java @@ -1,4 +1,4 @@ -package net.hobbyscience.housedb.dao; +package net.hobbyscience.housedb.types.serializers; import java.io.IOException; import java.time.OffsetDateTime; @@ -9,6 +9,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import net.hobbyscience.housedb.types.DataTriple; + public class DataTripleDeserializer extends StdDeserializer{ public static final Logger logger = Logger.getLogger(DataTripleDeserializer.class.getName()); public DataTripleDeserializer(){ diff --git a/housedb-dao/src/main/java/net/hobbyscience/housedb/dao/DataTripleSerializer.java b/housedb-dao/src/main/java/net/hobbyscience/housedb/types/serializers/DataTripleSerializer.java similarity index 90% rename from housedb-dao/src/main/java/net/hobbyscience/housedb/dao/DataTripleSerializer.java rename to housedb-dao/src/main/java/net/hobbyscience/housedb/types/serializers/DataTripleSerializer.java index 52bc1f3..5784031 100644 --- a/housedb-dao/src/main/java/net/hobbyscience/housedb/dao/DataTripleSerializer.java +++ b/housedb-dao/src/main/java/net/hobbyscience/housedb/types/serializers/DataTripleSerializer.java @@ -1,4 +1,4 @@ -package net.hobbyscience.housedb.dao; +package net.hobbyscience.housedb.types.serializers; import java.io.IOException; import java.time.format.DateTimeFormatter; @@ -7,6 +7,8 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import net.hobbyscience.housedb.types.DataTriple; + public class DataTripleSerializer extends StdSerializer{ /** diff --git a/housedb-dao/src/test/java/net/hobbyscience/housedb/dao/DataTripleTest.java b/housedb-dao/src/test/java/net/hobbyscience/housedb/dao/DataTripleTest.java index 5b5f266..8e89b57 100644 --- a/housedb-dao/src/test/java/net/hobbyscience/housedb/dao/DataTripleTest.java +++ b/housedb-dao/src/test/java/net/hobbyscience/housedb/dao/DataTripleTest.java @@ -2,6 +2,10 @@ import org.junit.jupiter.api.Test; +import net.hobbyscience.housedb.types.DataTriple; +import net.hobbyscience.housedb.types.serializers.DataTripleDeserializer; +import net.hobbyscience.housedb.types.serializers.DataTripleSerializer; + import static org.junit.jupiter.api.Assertions.*; import java.time.OffsetDateTime; diff --git a/z-api/src/main/java/net/hobbyscience/housedb/api/Entry.java b/z-api/src/main/java/net/hobbyscience/housedb/api/Entry.java index d67274a..a8ffb74 100644 --- a/z-api/src/main/java/net/hobbyscience/housedb/api/Entry.java +++ b/z-api/src/main/java/net/hobbyscience/housedb/api/Entry.java @@ -10,6 +10,9 @@ import net.hobbyscience.housedb.api.controllers.*; import net.hobbyscience.housedb.dao.*; import net.hobbyscience.housedb.jackson.*; +import net.hobbyscience.housedb.types.DataTriple; +import net.hobbyscience.housedb.types.serializers.DataTripleDeserializer; +import net.hobbyscience.housedb.types.serializers.DataTripleSerializer; import org.apache.tomcat.jdbc.pool.DataSource; import java.util.Base64; @@ -27,15 +30,24 @@ import io.javalin.plugin.openapi.OpenApiPlugin; import io.javalin.plugin.openapi.ui.ReDocOptions; import io.javalin.plugin.openapi.ui.SwaggerOptions; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.security.SecurityScheme.Type; public class Entry { private static final Logger logger = Logger.getLogger(Entry.class.getName()); public static OpenApiPlugin getOpenApiPlugin() { Info info = new Info().version("1.0").description("HouseDB API"); - OpenApiOptions ops = new OpenApiOptions(info) - .path("/swagger-docs") + Components components = new Components().addSecuritySchemes( + "bearerAuth", + new SecurityScheme().type(Type.HTTP).scheme("bearer").bearerFormat("JWT") + ); + + OpenApiOptions ops = new OpenApiOptions( () -> new OpenAPI().components(components).info(info) ); + ops.path("/swagger-docs") //.toJsonMapper(JacksonToJsonMapper.INSTANCE) .swagger(new SwaggerOptions("/swagger-ui")) .reDoc(new ReDocOptions("/redoc")) @@ -43,6 +55,7 @@ public static OpenApiPlugin getOpenApiPlugin() { doc.json("500", ErrorResponse.class); doc.json("503", ErrorResponse.class); }); + return new OpenApiPlugin(ops); } @@ -93,6 +106,11 @@ public static void main(String []args) { logger.log(Level.WARNING,"database error",e); } }) + .exception(NotAuthorized.class, (e, ctx ) -> { + logger.warning("Unauthorized access"); + logger.warning(e.getCause().getMessage()); + ctx.status(HttpServletResponse.SC_UNAUTHORIZED).json("Unauthorized Access"); + }) .exception(UnsupportedOperationException.class, (e,ctx) -> { ctx.status(HttpServletResponse.SC_NOT_IMPLEMENTED); logger.log(Level.WARNING,"error",e); @@ -108,17 +126,14 @@ public static void main(String []args) { var header = ctx.header(Header.AUTHORIZATION); if( header != null ){ // verification will be handled at the gateway - //val jwt = Jwts.parserBuilder().build().parseClaimsJws(ctx.header("Authorization")) - logger.info(header); + //val jwt = Jwts.parserBuilder().build().parseClaimsJws(ctx.header("Authorization")) var parts = header.split("\\\\s+"); - var jwt = parts[parts.length-1].split("\\."); - logger.info(""+jwt.length); + var jwt = parts[parts.length-1].split("\\."); var jwtClaims = Base64.getDecoder().decode(jwt[1]); var jsonClaims = om.readTree(jwtClaims); - //val user = jwt.subject() - logger.fine(jsonClaims.toString()); + //val user = jwt.subject() var user = om.treeToValue(jsonClaims.get("sub"),String.class); - logger.fine(user); + logger.fine(user); ctx.attribute("username",user); } else { ctx.attribute("username","guest"); diff --git a/z-api/src/main/java/net/hobbyscience/housedb/api/NotAuthorized.java b/z-api/src/main/java/net/hobbyscience/housedb/api/NotAuthorized.java new file mode 100644 index 0000000..93c812c --- /dev/null +++ b/z-api/src/main/java/net/hobbyscience/housedb/api/NotAuthorized.java @@ -0,0 +1,7 @@ +package net.hobbyscience.housedb.api; + +public class NotAuthorized extends RuntimeException{ + public NotAuthorized(String msg, Throwable err ){ + super(msg,err); + } +} diff --git a/z-api/src/main/java/net/hobbyscience/housedb/api/controllers/LocationController.java b/z-api/src/main/java/net/hobbyscience/housedb/api/controllers/LocationController.java index 80f728c..8bf92d2 100644 --- a/z-api/src/main/java/net/hobbyscience/housedb/api/controllers/LocationController.java +++ b/z-api/src/main/java/net/hobbyscience/housedb/api/controllers/LocationController.java @@ -3,12 +3,18 @@ import io.javalin.apibuilder.*; import io.javalin.http.*; import io.javalin.plugin.openapi.annotations.*; +import net.hobbyscience.housedb.api.NotAuthorized; import net.hobbyscience.housedb.dao.*; +import net.hobbyscience.housedb.dto.Location; +import java.sql.SQLException; import java.util.logging.Logger; +import javax.servlet.http.HttpServletResponse; import javax.sql.DataSource; +import org.jooq.exception.DataAccessException; + public class LocationController implements CrudHandler { public static final Logger logger = Logger.getLogger(LocationController.class.getName()); @@ -16,6 +22,9 @@ public class LocationController implements CrudHandler { tags = {"Locations"}, responses = { @OpenApiResponse(status="200", content = {@OpenApiContent( from = Location[].class,isArray=true)}) + }, + security = { + @OpenApiSecurity(name = "bearerAuth") } ) @Override @@ -23,7 +32,7 @@ public void getAll( Context ctx){ var ds = ctx.appAttribute(DataSource.class); try( var conn = ds.getConnection(); ){ var db = new HouseDb(conn,ctx.attribute("username")); - var locations = db.getAllLocations(); + var locations = db.locationDao().getAll(); ctx.json(locations); } catch( Exception err ){ throw new RuntimeException("Error retrieving locations",err); @@ -31,13 +40,30 @@ public void getAll( Context ctx){ } @OpenApi( - tags = {"Locations"}, + pathParams = { + @OpenApiParam(name="location-name",description = "Name of the location you're trying to look up.") + }, responses = { @OpenApiResponse(status="200", content = {@OpenApiContent( from = Location.class)}) - } + }, + security = { + @OpenApiSecurity(name = "bearerAuth") + }, + tags = {"Locations"} ) public void getOne( Context ctx, String locationName ){ - throw new UnsupportedOperationException("not implemented yet"); + var ds = ctx.appAttribute(DataSource.class); + try( var conn = ds.getConnection(); ){ + var db = new HouseDb(conn,ctx.attribute("username")); + var loc = db.locationDao().getByUniqueName(locationName); + if( loc.isPresent() ){ + ctx.json(loc.get()).contentType("application/json"); + } else { + ctx.result("Location not found").status(HttpServletResponse.SC_NOT_FOUND); + } + } catch( SQLException err ){ + throw new RuntimeException("Database error",err); + } } @OpenApi( @@ -45,6 +71,9 @@ public void getOne( Context ctx, String locationName ){ requestBody = @OpenApiRequestBody(content = {@OpenApiContent(from = Location.class)}), responses = { @OpenApiResponse(status="200", content = {@OpenApiContent( from = Location.class)}) + }, + security = { + @OpenApiSecurity(name = "bearerAuth") } ) public void create(Context ctx) { @@ -52,19 +81,30 @@ public void create(Context ctx) { var ds = ctx.appAttribute(DataSource.class); try( var conn = ds.getConnection(); ){ var db = new HouseDb(conn,ctx.attribute("username")); - db.saveLocation(loc); - } catch( Exception err ){ + db.locationDao().save(loc); + } catch (DataAccessException err ){ + if( err.getLocalizedMessage().contains("no CREATE")){ + throw new NotAuthorized("Location", err); + } + } + catch( Exception err ){ throw new RuntimeException("Error creating location",err); } } @OpenApi( + security = { + @OpenApiSecurity(name = "bearerAuth") + }, tags = {"Locations"} ) public void update(Context ctx, String locationName){ throw new UnsupportedOperationException("not implemented yet"); } @OpenApi( + security = { + @OpenApiSecurity(name = "bearerAuth") + }, tags = {"Locations"} ) public void delete(Context ctx, String locationName){ throw new UnsupportedOperationException("not implemented yet"); } diff --git a/z-api/src/main/java/net/hobbyscience/housedb/api/controllers/TimeSeriesController.java b/z-api/src/main/java/net/hobbyscience/housedb/api/controllers/TimeSeriesController.java index 347387e..21064cb 100644 --- a/z-api/src/main/java/net/hobbyscience/housedb/api/controllers/TimeSeriesController.java +++ b/z-api/src/main/java/net/hobbyscience/housedb/api/controllers/TimeSeriesController.java @@ -3,6 +3,8 @@ import io.javalin.http.*; import io.javalin.plugin.openapi.annotations.*; import net.hobbyscience.housedb.dao.*; +import net.hobbyscience.housedb.dto.TimeSeries; + import javax.sql.DataSource; import org.jooq.exception.*; import java.time.*; @@ -42,6 +44,9 @@ public void getAll(Context ctx){ responses = { @OpenApiResponse(status="200", content = {@OpenApiContent( from = TimeSeries.class)}), @OpenApiResponse(status="404", content = {@OpenApiContent( from = NotFoundResponse.class)}) + }, + security = { + @OpenApiSecurity(name = "bearerAuth") } ) public void getOne(Context ctx, String locationName){ @@ -95,11 +100,17 @@ public void create(Context ctx){ } @OpenApi( + security = { + @OpenApiSecurity(name = "bearerAuth") + }, tags = {"TimeSeries"} ) public void update(Context ctx, String locationName){ throw new UnsupportedOperationException("not implemented yet"); } @OpenApi( + security = { + @OpenApiSecurity(name = "bearerAuth") + }, tags = {"TimeSeries"} ) public void delete(Context ctx , String locationName){ throw new UnsupportedOperationException("not implemented yet"); }