securityFeature :: ServerEnv
                -> StateComponent AcidState SecurityState
                -> SecurityFeature
securityFeature env securityState =
    SecurityFeature{..}
  where
    securityFeatureInterface = (emptyHackageFeature "security") {
        featureDesc        = "TUF Security"
      , featureState       = [abstractAcidStateComponent securityState]
      , featureReloadFiles = updateRootMirrorsAndKeys env securityState
      , featurePostInit    = updateRootMirrorsAndKeys env securityState
                          >> setupResignCronJob env securityState
      , featureResources   = [
            resourceTimestamp
          , resourceSnapshot
          , resourceRoot
          , resourceMirrors
          ]
      }

    resourceTimestamp = (secResourceAt Sec.repoLayoutTimestamp) {
        resourceDesc = [(GET, "Get TUF timestamp")]
      , resourceGet  = [("json", serveFromState securityTimestamp)]
      }
    resourceSnapshot = (secResourceAt Sec.repoLayoutSnapshot) {
        resourceDesc = [(GET, "Get TUF snapshot")]
      , resourceGet  = [("json", serveFromState securitySnapshot)]
      }
    resourceRoot = (secResourceAt Sec.repoLayoutRoot) {
        resourceDesc = [(GET, "Get TUF root")]
      , resourceGet  = [("json", serveFromState securityRoot)]
      }
    resourceMirrors = (secResourceAt Sec.repoLayoutMirrors) {
        resourceDesc = [(GET, "Get TUF mirrors")]
      , resourceGet  = [("json", serveFromState securityMirrors)]
      }

    serveFromState :: (IsTUFFile a, ToMessage a)
                   => (SecurityStateFiles -> a)
                   -> DynamicPath
                   -> ServerPartE Response
    serveFromState file _ = do
      msfiles <- queryState securityState GetSecurityFiles
      case msfiles of
        Nothing -> errNotFound "Security files not available"
                     [MText $ "The repository is not currently using TUF "
                           ++ "security so the security files are not "
                           ++ "available."]
        Just sfiles -> do
          let tufFile = file sfiles
              eTag    = ETag $ show (tufFileHashMD5 tufFile)
          -- Higher max-age values result in higher cache hit ratios, but also
          -- in higher likelyhood of cache incoherence problems (and of course in
          -- higher likelyhood of caches beind out of date with updates to the
          -- central server).
          cacheControl [Public, NoTransform, maxAgeMinutes 1] eTag
          enableRange
          return $ toResponse tufFile
securityFeature :: ServerEnv
                -> StateComponent AcidState SecurityState
                -> SecurityFeature
securityFeature env securityState =
    SecurityFeature{..}
  where
    securityFeatureInterface = (emptyHackageFeature "security") {
        featureDesc        = "TUF Security"
      , featureState       = [abstractAcidStateComponent securityState]
      , featureReloadFiles = updateRootMirrorsAndKeys env securityState
      , featurePostInit    = updateRootMirrorsAndKeys env securityState
                          >> setupResignCronJob env securityState
      , featureResources   = [
            resourceTimestamp
          , resourceSnapshot
          , resourceRoot
          , resourceMirrors
          ]
      }

    resourceTimestamp = (secResourceAt Sec.repoLayoutTimestamp) {
        resourceDesc = [(GET, "Get TUF timestamp")]
      , resourceGet  = [("json", serveFromState securityTimestamp)]
      }
    resourceSnapshot = (secResourceAt Sec.repoLayoutSnapshot) {
        resourceDesc = [(GET, "Get TUF snapshot")]
      , resourceGet  = [("json", serveFromState securitySnapshot)]
      }
    resourceRoot = (secResourceAt Sec.repoLayoutRoot) {
        resourceDesc = [(GET, "Get TUF root")]
      , resourceGet  = [("json", serveFromState securityRoot)]
      }
    resourceMirrors = (secResourceAt Sec.repoLayoutMirrors) {
        resourceDesc = [(GET, "Get TUF mirrors")]
      , resourceGet  = [("json", serveFromState securityMirrors)]
      }

    serveFromState :: (IsTUFFile a, ToMessage a)
                   => (SecurityStateFiles -> a)
                   -> DynamicPath
                   -> ServerPartE Response
    serveFromState file _ = do
      msfiles <- queryState securityState GetSecurityFiles
      case msfiles of
        Nothing -> errNotFound "Security files not available"
                     [MText $ "The repository is not currently using TUF "
                           ++ "security so the security files are not "
                           ++ "available."]
        Just sfiles -> do
          let tufFile = file sfiles
              eTag    = ETag $ show (tufFileHashMD5 tufFile)
          -- Higher max-age values result in higher cache hit ratios, but also
          -- in higher likelyhood of cache incoherence problems (and of course in
          -- higher likelyhood of caches beind out of date with updates to the
          -- central server).
          cacheControl [Public, NoTransform, maxAgeMinutes 1] eTag
          enableRange
          return $ toResponse tufFile
securityFeature env securityState =
    SecurityFeature{..}
  where
    securityFeatureInterface = (emptyHackageFeature "security") {
        featureDesc        = "TUF Security"
      , featureState       = [abstractAcidStateComponent securityState]
      , featureReloadFiles = updateRootMirrorsAndKeys env securityState
      , featurePostInit    = updateRootMirrorsAndKeys env securityState
                          >> setupResignCronJob env securityState
      , featureResources   = [
            resourceTimestamp
          , resourceSnapshot
          , resourceRoot
          , resourceMirrors
          ]
      }

    resourceTimestamp = (secResourceAt Sec.repoLayoutTimestamp) {
        resourceDesc = [(GET, "Get TUF timestamp")]
      , resourceGet  = [("json", serveFromState securityTimestamp)]
      }
    resourceSnapshot = (secResourceAt Sec.repoLayoutSnapshot) {
        resourceDesc = [(GET, "Get TUF snapshot")]
      , resourceGet  = [("json", serveFromState securitySnapshot)]
      }
    resourceRoot = (secResourceAt Sec.repoLayoutRoot) {
        resourceDesc = [(GET, "Get TUF root")]
      , resourceGet  = [("json", serveFromState securityRoot)]
      }
    resourceMirrors = (secResourceAt Sec.repoLayoutMirrors) {
        resourceDesc = [(GET, "Get TUF mirrors")]
      , resourceGet  = [("json", serveFromState securityMirrors)]
      }

    serveFromState :: (IsTUFFile a, ToMessage a)
                   => (SecurityStateFiles -> a)
                   -> DynamicPath
                   -> ServerPartE Response
    serveFromState file _ = do
      msfiles <- queryState securityState GetSecurityFiles
      case msfiles of
        Nothing -> errNotFound "Security files not available"
                     [MText $ "The repository is not currently using TUF "
                           ++ "security so the security files are not "
                           ++ "available."]
        Just sfiles -> do
          let tufFile = file sfiles
              eTag    = ETag $ show (tufFileHashMD5 tufFile)
          -- Higher max-age values result in higher cache hit ratios, but also
          -- in higher likelyhood of cache incoherence problems (and of course in
          -- higher likelyhood of caches beind out of date with updates to the
          -- central server).
          cacheControl [Public, NoTransform, maxAgeMinutes 1] eTag
          enableRange
          return $ toResponse tufFile