Dear Gitlab users, due to maintenance reasons, Gitlab will not be available on Thursday 30.09.2021 from 5:00 pm to approximately 5:30 pm.

Commit 82242211 authored by Andreas Wagner's avatar Andreas Wagner
Browse files

Add writeDOI conf and Form-Mode submission.

parent 8d5a93b5
This diff is collapsed.
......@@ -5,6 +5,7 @@
"APIRoot": "/api/v1",
"FileAPI": "/file",
"WebhookAPI": "/hooks/receivers/github/events/",
"Secret": "ThIs_iS_NoT_ReAlLy_a_sMaRt_sEcReT!!",
"Log": {
"file": "t2z.log",
"level": "Info"
......@@ -15,15 +16,27 @@
"prefix": "10.5072/zenodo."
},
"Git": {
"host": "https://api.github.com",
"token": "aBcDeFgHiJkLmNoPqRsTuVwXyZ",
"secret": "ThIs_iS_NoT_ReAlLy_a_sMaRt_sEcReT!!",
"host": "https://api.github.com",
"repo": "octocat/hello-world",
"branch": "public",
"hookUser": "foobar",
"triggerPhrase": "(push to zenodo)",
"dontpublishPhrase": "test"
},
"WriteDOI": {
"mode": "add",
"parentPath": "/TEI/teiHeader/fileDesc/publicationStmt/idno",
"elementType": "idno",
"otherAttributes": [
{
"attName": "type",
"value": "DOI"
}
],
"position": "firstChild",
"attributeName": ""
},
"metadata": {
"fields": [
{
......
......@@ -28,9 +28,11 @@ func Configure(Config *tei2zenodo.Config) error {
viper.SetDefault("WebhookAPI", "/hooks/receivers/github/events/")
viper.SetDefault("Log", map[string]string{"file": "t2z.log", "level": "Info"})
viper.SetDefault("Zenodo", map[string]string{"prefix": "10.5072/zenodo.",
"host": "https://sandbox.zenodo.org",
"token": "jLsTkUOMDU2fnGdGivGbB9TnMkPcADhIzEKHqqoVzMRsdYEC0Sqqfz72SPpt"})
"token": ""})
viper.SetDefault("Git", map[string]string{"host": "https://api.github.com",
"token": "",
"repo": "octocat/hello-world",
......@@ -40,6 +42,17 @@ func Configure(Config *tei2zenodo.Config) error {
"commitDontpublishPhrase": "test",
})
var doiWriteAtts []map[string]string
doiWriteAtts = append(doiWriteAtts, map[string]string{"attName": "type", "value": "DOI"})
viper.SetDefault("WriteDOI", map[string]interface{}{"mode": "add",
"parentPath": "/TEI/teiHeader/fileDesc/publicationStmt/idno",
"elementType": "idno",
"position": "firstChild",
"attributeName": "",
"otherAttributes": doiWriteAtts,
})
viper.SetEnvPrefix("T2Z")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv()
......
......@@ -40,7 +40,9 @@ func SetupRoutes(conf tei2zenodo.Config) *gin.Engine {
// c.String(http.StatusOK, "Service homepage with page "+page+".")
})
router.GET("/index.html", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", nil)
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"metadataConf": conf.Metadata,
})
})
APIv1 := router.Group(conf.APIRoot)
......@@ -102,6 +104,31 @@ func SetupRoutes(conf tei2zenodo.Config) *gin.Engine {
r = file
}
// If Secret is configured, check signature
if conf.Secret != "" {
// Read signature from form field or query parameter
signature := c.PostForm("signature")
if signature == "" {
signature = c.Query("signature")
}
result := ValidMAC(streamToByte(r), []byte(signature), []byte(conf.Secret))
if !result {
if conf.Verbose {
mac := hmac.New(sha1.New, []byte(conf.Secret))
mac.Write(streamToByte(r))
expectedMAC := []byte("sha1=" + hex.EncodeToString(mac.Sum(nil)))
log.Warnf("File upload request with invalid HMAC signature received, ignoring. Sign.: %s, Message hash: %s", signature, expectedMAC)
} else {
log.Warnf("File upload request with invalid HMAC signature received, ignoring")
}
AbortMsg(403, tei2zenodo.NewError("errWebhook", fmt.Sprintf("File upload request with invalid HMAC signature received, ignoring"), 403, nil), c)
return
}
log.Debugf(" File upload request HMAC signature verified")
r.Seek(0, 0)
}
// If we still have no filename, parse one from the file's contents
if filename == "" {
log.Warnf(" No filename specified. Creating one from the file's content...")
......@@ -117,6 +144,43 @@ func SetupRoutes(conf tei2zenodo.Config) *gin.Engine {
}
myDeposit.Filename = filename
// Read writeDOImode from Form field, Query parameter or File field
writeDOImode := c.PostForm("writeDOImode")
if writeDOImode == "" {
writeDOImode = c.Query("writeDOImode")
}
// Read writeDOIparentPath from Form field, Query parameter or File field
writeDOIparentPath := c.PostForm("writeDOIparentPath")
if writeDOIparentPath == "" {
writeDOIparentPath = c.Query("writeDOIparentPath")
}
// Read writeDOIposition from Form field, Query parameter or File field
writeDOIposition := c.PostForm("writeDOIposition")
if writeDOIposition == "" {
writeDOIposition = c.Query("writeDOIposition")
}
// Read writeDOIelementType from Form field, Query parameter or File field
writeDOIelementType := c.PostForm("writeDOIelementType")
if writeDOIelementType == "" {
writeDOIelementType = c.Query("writeDOIelementType")
}
// Read writeDOIattributeName from Form field, Query parameter or File field
writeDOIattributeName := c.PostForm("writeDOIattributeName")
if writeDOIattributeName == "" {
writeDOIattributeName = c.Query("writeDOIattributeName")
}
// Read zURL from Form field, Query parameter or File field
zURL := c.PostForm("zURL")
if zURL == "" {
zURL = c.Query("zURL")
}
// Read zPrefix from Form field, Query parameter or File field
zPrefix := c.PostForm("zPrefix")
if zPrefix == "" {
zPrefix = c.Query("zPrefix")
}
// Get doPublish from request (false if not set)
doPublish := c.PostForm("doPublish")
if doPublish == "" {
......@@ -150,6 +214,9 @@ func SetupRoutes(conf tei2zenodo.Config) *gin.Engine {
r.Seek(0, 0)
md.DOI = ""
// Before processing, nothing has changed
myDeposit.Changed = false
ZPFErr := zenodo.ProcessFile(r, doi, &md, &myDeposit, &conf)
if ZPFErr != nil {
log.Errorf("Problem processing file %s: %+v", myDeposit.Filename, ZPFErr)
......@@ -164,7 +231,11 @@ func SetupRoutes(conf tei2zenodo.Config) *gin.Engine {
if len(DOIs) == 1 {
c.Header("Location", DOIs[0])
}
c.JSON(http.StatusCreated, gin.H{"doi": DOIs})
if myDeposit.Changed {
c.XML(http.StatusOK, myDeposit.FileContent)
} else {
c.JSON(http.StatusCreated, gin.H{"doi": DOIs})
}
})
}
if conf.WebhookAPI != "" {
......@@ -187,12 +258,12 @@ func SetupRoutes(conf tei2zenodo.Config) *gin.Engine {
r = strings.NewReader(file)
// If Secret is configured, check signature
if conf.Git.Secret != "" {
if conf.Secret != "" {
signature := c.Request.Header.Get("X-Hub-Signature")
result := ValidMAC([]byte(file), []byte(signature), []byte(conf.Git.Secret))
result := ValidMAC([]byte(file), []byte(signature), []byte(conf.Secret))
if !result {
if conf.Verbose {
mac := hmac.New(sha1.New, []byte(conf.Git.Secret))
mac := hmac.New(sha1.New, []byte(conf.Secret))
mac.Write([]byte(file))
expectedMAC := []byte("sha1=" + hex.EncodeToString(mac.Sum(nil)))
log.Warnf("Webhook with invalid HMAC signature received, ignoring. Sign.: %s, Message hash: %s", signature, expectedMAC)
......@@ -227,7 +298,7 @@ func SetupRoutes(conf tei2zenodo.Config) *gin.Engine {
var DOIs []string
var SHAs []string
// Send each file to processing
// Send each file to processing (parsing, mixing, uploading)
for f := range files {
log.Printf("--- Process file '%s' ---", f)
......@@ -271,6 +342,9 @@ func SetupRoutes(conf tei2zenodo.Config) *gin.Engine {
r.Seek(0, 0)
md.DOI = ""
// Before processing, nothing has changed
myDeposit.Changed = false
// Upload (and publish?) file to zenodo
ZPFErr := zenodo.ProcessFile(r, doi, &md, &myDeposit, &conf)
if ZPFErr != nil {
......@@ -323,3 +397,9 @@ func ValidMAC(message, messageMAC, key []byte) bool {
expectedMAC := []byte("sha1=" + hex.EncodeToString(mac.Sum(nil)))
return hmac.Equal(messageMAC, expectedMAC)
}
func streamToByte(stream io.Reader) []byte {
buf := new(bytes.Buffer)
buf.ReadFrom(stream)
return buf.Bytes()
}
......@@ -289,43 +289,84 @@ func MixinDOI(r io.Reader, doi string, c *tei2zenodo.Config) (string, error) {
log.Errorf("Could not parse xml.")
return "", tei2zenodo.NewError("errParse", "could not parse xml", 500, err)
}
pStmt := doc.FindElement(`/TEI/teiHeader/fileDesc/publicationStmt`)
if pStmt == nil {
log.Errorf("XML file had no /TEI/teiHeader/fileDesc/publicationStmt element.")
err := fmt.Errorf("XML file had no /TEI/teiHeader/fileDesc/publicationStmt element")
return "", tei2zenodo.NewError("errParse", "XML file had no /TEI/teiHeader/fileDesc/publicationStmt element", 500, err)
// We need the parent element anyway
// (unless we're not modifying the file at all, but in this case we shouldn't even be here)
pElmnt := doc.FindElement(c.WriteDOI.ParentPath) // Default `/TEI/teiHeader/fileDesc/publicationStmt`
if pElmnt == nil {
log.Errorf("Supposed to write new DOI but XML file had no element to be found at configured parent path %s.", c.WriteDOI.ParentPath)
return "", tei2zenodo.NewError("errParse", fmt.Sprintf("supposed to write new DOI but XML file had no element to be found at configured parent path %s", c.WriteDOI.ParentPath), 500, nil)
}
topLevelIdno := pStmt.FindElement(`./idno`)
if topLevelIdno == nil { // publicationStmt does not contain any <idno> element -> add one as last child of pStmt
if c.Verbose {
log.Debugf("No idno element present. Create one.")
// Try to find the first child element of type ElementType
tElmnt := pElmnt.FindElement(c.WriteDOI.ElementType)
if tElmnt == nil || c.WriteDOI.Mode == "add" { // If mode = "add" or pElemnt/elementType cannot be found ...
tElmnt := etree.NewElement(c.WriteDOI.ElementType) // ... create a new element ...
if tElmnt == nil {
log.Errorf("Problem creating %s element.", c.WriteDOI.ElementType)
return "", tei2zenodo.NewError("errParse", fmt.Sprintf("problem creating %s element", c.WriteDOI.ElementType), 500, nil)
}
for i := 0; i < len(c.WriteDOI.OtherAttributes); i++ { // ... make sure all required attributes are there ...
oAttr := tElmnt.CreateAttr(c.WriteDOI.OtherAttributes[i].AttName, c.WriteDOI.OtherAttributes[i].Value)
if oAttr == nil {
log.Errorf("Problem creating %s/@%s='%s'.", c.WriteDOI.ElementType, c.WriteDOI.OtherAttributes[i].AttName, c.WriteDOI.OtherAttributes[i].Value)
return "", tei2zenodo.NewError("errParse", fmt.Sprintf("problem creating %s/@%s='%s'", c.WriteDOI.ElementType, c.WriteDOI.OtherAttributes[i].AttName, c.WriteDOI.OtherAttributes[i].Value), 500, nil)
}
}
targetIdno := pStmt.CreateElement("idno")
targetIdno.CreateAttr("type", "DOI")
targetIdno.CreateText(doi)
} else {
subIdnos := topLevelIdno.FindElement(`./idno`)
if subIdnos != nil { // publicationStmt contains <idno> with sub-<idno>s -> add doi idno as last child of parentIdno
oldIdno := topLevelIdno.FindElement(`./idno[@type='DOI']`)
if oldIdno != nil {
oldIdnoIdx := oldIdno.Index()
topLevelIdno.RemoveChildAt(oldIdnoIdx)
if c.WriteDOI.AttributeName != "" { // ... add doi as attribute ...
tAttr := tElmnt.CreateAttr(c.WriteDOI.AttributeName, doi)
if tAttr == nil {
log.Errorf("Problem creating %s/@%s='%s'.", c.WriteDOI.ElementType, c.WriteDOI.AttributeName, doi)
return "", tei2zenodo.NewError("errParse", fmt.Sprintf("problem creating %s/@%s='%s'", c.WriteDOI.ElementType, c.WriteDOI.AttributeName, doi), 500, nil)
}
} else { // ... or as text node ...
tText := tElmnt.CreateText(doi)
if tText == nil {
log.Errorf("Problem creating text '%s' at %s.", doi, c.WriteDOI.ElementType)
return "", tei2zenodo.NewError("errParse", fmt.Sprintf("problem creating text '%s' at %s", doi, c.WriteDOI.ElementType), 500, nil)
}
targetIdno := topLevelIdno.CreateElement("idno")
targetIdno.CreateAttr("type", "DOI")
targetIdno.CreateText(doi)
} else { // publicationStmt contains one (or several) <idno> elements -> add doi at the first idno element's position
oldIdno := pStmt.FindElement(`./idno[@type='DOI']`)
if oldIdno != nil {
oldIdnoIdx := oldIdno.Index()
pStmt.RemoveChildAt(oldIdnoIdx)
}
if c.WriteDOI.Position == "firstChild" { // ... then we attach the element at the right position
pElmnt.InsertChildAt(0, tElmnt)
} else if c.WriteDOI.Position == "lastChild" {
pElmnt.InsertChildAt(len(pElmnt.FindElements(`/*`))+100, tElmnt)
} else {
log.Errorf("Invalid value %s in WriteDOI.Position configuration (must be 'firstChild' or 'lastChild').", c.WriteDOI.Position)
return "", tei2zenodo.NewError("errBadConfig", fmt.Sprintf("invalid value %s in WriteDOI.Position configuration (must be 'firstChild' or 'lastChild')", c.WriteDOI.Position), 500, nil)
}
} else if c.WriteDOI.Mode == "replace" { // If, on the other hand, we have mode ="replace" AND pElmnt/elementType is an element
if c.WriteDOI.AttributeName != "" { // if the value is in an attribute ...
tAttr := tElmnt.CreateAttr(c.WriteDOI.AttributeName, doi) // ... then we only replace this attribute ...
if tAttr == nil {
log.Errorf("Problem creating %s/%s/@%s='%s'.", c.WriteDOI.ParentPath, c.WriteDOI.ElementType, c.WriteDOI.AttributeName, doi)
return "", tei2zenodo.NewError("errParse", fmt.Sprintf("problem creating %s/%s/@%s='%s'", c.WriteDOI.ParentPath, c.WriteDOI.ElementType, c.WriteDOI.AttributeName, doi), 500, nil)
}
for i := 0; i < len(c.WriteDOI.OtherAttributes); i++ { // ... and we make sure all other required attributes are there
oAttr := tElmnt.CreateAttr(c.WriteDOI.OtherAttributes[i].AttName, c.WriteDOI.OtherAttributes[i].Value)
if oAttr == nil {
log.Errorf("Problem creating %s/%s/@%s='%s'.", c.WriteDOI.ParentPath, c.WriteDOI.ElementType, c.WriteDOI.OtherAttributes[i].AttName, c.WriteDOI.OtherAttributes[i].Value)
return "", tei2zenodo.NewError("errParse", fmt.Sprintf("problem creating %s/%s/@%s='%s'", c.WriteDOI.ParentPath, c.WriteDOI.ElementType, c.WriteDOI.OtherAttributes[i].AttName, c.WriteDOI.OtherAttributes[i].Value), 500, nil)
}
}
} else {
tElmnt.SetText(doi) // else the value is in the text node, and we replace this text ...
if tElmnt.Text() != doi {
log.Errorf("Problem creating text '%s' at %s/%s.", doi, c.WriteDOI.ParentPath, c.WriteDOI.ElementType)
return "", tei2zenodo.NewError("errParse", fmt.Sprintf("problem creating text '%s' at %s/%s", doi, c.WriteDOI.ParentPath, c.WriteDOI.ElementType), 500, nil)
}
for i := 0; i < len(c.WriteDOI.OtherAttributes); i++ { // ... and we make sure all required attributes are there
oAttr := tElmnt.CreateAttr(c.WriteDOI.OtherAttributes[i].AttName, c.WriteDOI.OtherAttributes[i].Value)
if oAttr == nil {
log.Errorf("Problem creating %s/%s/@%s='%s'.", c.WriteDOI.ParentPath, c.WriteDOI.ElementType, c.WriteDOI.OtherAttributes[i].AttName, c.WriteDOI.OtherAttributes[i].Value)
return "", tei2zenodo.NewError("errParse", fmt.Sprintf("problem creating %s/%s/@%s='%s'", c.WriteDOI.ParentPath, c.WriteDOI.ElementType, c.WriteDOI.OtherAttributes[i].AttName, c.WriteDOI.OtherAttributes[i].Value), 500, nil)
}
}
targetIdno := etree.NewElement("idno")
targetIdno.CreateAttr("type", "DOI")
targetIdno.CreateText(doi)
pStmt.InsertChildAt(topLevelIdno.Index(), targetIdno)
}
} else { // We have an invalid mode
log.Errorf("Invalid value %s in WriteDOI.mode configuration (must be 'add', 'replace' or 'ignore').", c.WriteDOI.Mode)
return "", tei2zenodo.NewError("errBadConfig", fmt.Sprintf("invalid value %s in WriteDOI.mode configuration (must be 'add', 'replace' or 'ignore')", c.WriteDOI.Mode), 500, nil)
}
output, err := doc.WriteToString()
......
......@@ -76,17 +76,29 @@ func ProcessFile(r io.ReadSeeker, doi string, md *tei2zenodo.ZMetadata, myDeposi
md.DOI = newDOI
}
log.Printf("--- Add new DOI to document ---")
newfile, MXErr := t2zxml.MixinDOI(r, md.DOI, conf)
if MXErr != nil {
log.Errorf("Error mixing new DOI into document: %v", MXErr)
return tei2zenodo.NewError("errInternal", fmt.Sprintf("error mixing new DOI into document: %s", MXErr), 500, MXErr)
}
// log.Printf("Here is the new file:\n%s", newfile[:10000])
myDeposit.FileContent = newfile
r = bytes.NewReader([]byte(newfile))
if conf.WriteDOI.Mode == "add" || conf.WriteDOI.Mode == "replace" {
log.Printf("--- Add new DOI to document ---")
newfile, MXErr := t2zxml.MixinDOI(r, md.DOI, conf)
if MXErr != nil {
log.Errorf("Error mixing new DOI into document: %v", MXErr)
return tei2zenodo.NewError("errInternal", fmt.Sprintf("error mixing new DOI into document: %s", MXErr), 500, MXErr)
}
// log.Printf("Here is the new file:\n%s", newfile[:10000])
myDeposit.FileContent = newfile
r = bytes.NewReader([]byte(newfile))
r.Seek(0, 0)
r.Seek(0, 0)
} else {
log.Printf("--- Skip adding new DOI to document ---")
buf := new(strings.Builder)
_, err := io.Copy(buf, r)
if err != nil {
log.Errorf("Could not read file.")
return tei2zenodo.NewError("errParse", "could not read file", 500, err)
}
myDeposit.FileContent = buf.String()
r.Seek(0, 0)
}
log.Printf("--- Upload to zenodo ---")
uploadFilename := strings.Replace(myDeposit.Filename, "/", "_", -1) // We replace the / (SOLIDUS, U+002F) with ∕ (DIVISION SLASH, U+2215)
......
......@@ -10,6 +10,8 @@ type Config struct {
APIRoot string
FileAPI string
WebhookAPI string
Secret string
WriteDOI WriteDOIConfig
Zenodo ZenodoConfig
Git GitConfig
Metadata MetadataConfig
......@@ -17,6 +19,22 @@ type Config struct {
T2ZCommitMessage string // This is not configurable in fact but stored here to be passed around among modules. Stores the commit message that this service creates and is set in conf.go
}
// WriteDOIConfig specifies if and how a new DOI value is written to the TEI file before processing
type WriteDOIConfig struct {
Mode string // 'add' or 'replace' (which actually also adds the element in question if none is present)
ParentPath string
ElementType string
Position string // 'firstChild' or 'lastChild'
AttributeName string // if the DOI goes into an attribute rather than a text node
OtherAttributes []AttributeSpec // if we need to add more attributes to an element that we're creating
}
// AttributeSpec specifies an (xml) attribute and its value
type AttributeSpec struct {
AttName string
Value string
}
// LoggingConfig specifies all the parameters needed for logging.
type LoggingConfig struct {
File string
......@@ -34,7 +52,6 @@ type ZenodoConfig struct {
type GitConfig struct {
Host string
Token string
Secret string
Repo string
Branch string
HookUser string
......@@ -56,6 +73,7 @@ type metadataField struct {
// Deposit stores a zenodo Deposit, part of it is returned by this service
type Deposit struct {
Changed bool
CommitSHA string
ConceptDOI string `json:"conceptdoi"`
ConceptRecID string `json:"conceptrecid"`
......
<!DOCTYPE html>
<html>
<head>
<head>
<title>TEI2Zenodo Web service</title>
<!--
<link rel="stylesheet" type="text/css" href="resources/css/bootstrap.min.css"/>
<link rel="stylesheet" type="text/css" href="resources/css/bootstrap-responsive.min.css"/>
<link rel="stylesheet" type="text/css" href="resources/css/style.css"/>
-->
<!--
<script type="text/javascript" src="$shared/resources/scripts/bootstrap.min.js"/>
-->
</head>
<body>
<div class="navbar">
<p style="float: right; margin-top: 0.8em">
<!--
DOI: <a href="https://doi.org/10.5281/zenodo.2604391">
10.5281/zenodo.2604391
</a> |
<a href="changelog.html">Version 1.4.0</a> |
-->
<a href="https://gitlab.gwdg.de/rg-mpg-de/tei2zenodo">Gitlab</a>
</p>
<!--
<p style="float: left; margin-top: 0.8em">
<a href="index.html">About</a> |
<a href="documentation.html">Documentation</a> |
<a href="examples.html">Examples</a> |
<a href="dh.html">Digital Humanities</a>
</p>
-->
</div>
<div class="main">
<div class="page-header">
<h1>TEI2Zenodo</h1>
<p>A generic webservice to to quickly push <a href="https://tei-c.org/guidelines/p5/">TEI XML</a> files
to <a href="https://about.zenodo.org/">zenodo</a> deposits, thereby assigning them a
<a href="https://www.doi.org/">DOI identifier</a> and committing them to long-term archival.
It offers a <a href="https://github.com/">gihub</a> integration, listening to
<a href="https://developer.github.com/webhooks/">webhooks</a> sent by github and updating the TEI files
in the repository with the new zenodo DOI identifiers.</p>
</div>
<div class="row-fluid">
<div id="quickstart">
<h2>Quickstart</h2>
<p>
With the TEI2Zenodo webservice you can submit TEI files to Zenodo deposits.
Based on a simple configuration using XPATH expressions, it extracts metadata
to be used in the zenodo deposit description from the TEI file.
The webservice can be used with direct POST or form-style POST requests,
or it can be used via github webhooks.
</p>
<p>
Note that Zenodo requires some metadata fields to be present and to use a controlled
vocabulary. Since this webservice cannot perform more than very simple XPath operations,
it cannot create the required terms and instead presupposes that the TEI files that are
being submitted make use of this controlled vocabulary, a presupposition that goes
beyond what the TEI guidelines recommend. Alternatively, you can hardcode a fixed value
for such fields in your configuration. The former approach could, for instance, be applied
to the contributor roles, where the service could look up the
<code>//titleStmt/editor/@type</code> value, but requires this value to be one of a specific
list of values <h href="https://developers.zenodo.org/#deposit-metadata">defined by zenodo</a>.
The second approach on the other hand could, for example, be used to specify that the
"upload_type" should always be "publication", no matter what. For more details, please see
the <a href="https://gitlab.gwdg.de/rg-mpg-de/tei2zenodo/-/blob/master/README.md">general
documentation</a> and the
<a href="https://gitlab.gwdg.de/rg-mpg-de/tei2zenodo/-/raw/master/configs/config.json.tpl">configuration
file template</a>.
</p>
</div>
<div id="form-style">
<h3>Form-style POST requests</h3>
<p>Example for the form-style POST API.</p>
<form action="api/v1/file" method="post" enctype="multipart/form-data" style="background: #F5F5F5; padding: 1em; border: 1px solid #9F9F9F;">
<div class="control-group">
<div class="controls">
<label>
Zenodo token: <input type="text" name="ztoken" value=""/>
</label>
</div>
<div class="controls">
<label>
Publish zenodo deposit: <input type="checkbox" name="doPublish"/>
</label>
</div>
<div class="controls">
<label>
Upload TEI file: <input name="file" type="file" size="50" accept="application/tei+xml" onchange="this.form.submit()"/>
</label>
</div>
</div>
</form>
</div>
<div id="direct-post">
<h3>Direct POST requests</h3>
<p>
You can also submit direct POST requests to the <code>file</code> API endpoint. Where this
actually is, depends on your configuration. By default, it is at <code>`hostname`:8081/api/v1/file</code>
</p>
<p>
(a) The <code>Content-Type</code> HTTP header should have a value of <code>application/xml</code> and the request
body should directly contain your file. Options are specified as query parameters:
specify a filename with the <code>filename</code> query parameter and use the <code>doPublish</code>
query parameter (set to either <code>True</code> or <code>False</code> whether you want zenodo to publish
the deposit or to leave it in editable state. (In the latter case, you can edit and publish it manually
if you log in to zenodo and go to your Uploads.)
</p>
<p>
(b) Alternatively, you can send a <code>multipart/form-data</code> request. (That would be a <code>Content-Type</code>
header of "multipart/form-data" plus some boundary string appended with a semicolon, e.g.
<code>multipart/form-data;boundary="myboundary" </code>.) The form fields are then called <code>filename</code>,
<code>file</code> and <code>doPublish</code>.
</p>
</div>
</div>
<div class="navbar">
<p style="float: left; margin-top: 0.8em">
Credits: <a href="https://www.rg.mpg.de">Max Plack Institute for European Legal History</a> (<a href="https://twitter.com/rg_mpg">@rg_mpg</a>)
<br/>Research Software Engineering: <a href="https://orcid.org/0000-0003-1835-1653">Andreas Wagner</a>
<br/>Software licensed under <a href="http://opensource.org/licenses/MIT">MIT</a>, content and documentation licensed under <a href="https://creativecommons.org/licenses/by/4.0/">CC-BY 4.0</a>
</p>
</div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>TEI2Zenodo Web service</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS file -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">
</head>
<body>
<div class="navbar">
<p style="float: right; margin-top: 0.8em">
<!--
DOI: <a href="https://doi.org/10.5281/zenodo.2604391">
10.5281/zenodo.2604391
</a> |
<a href="changelog.html">Version 1.4.0</a> |
-->
<a href="https://gitlab.gwdg.de/rg-mpg-de/tei2zenodo">Gitlab</a>
</p>
<!--
<p style="float: left; margin-top: 0.8em">
<a href="index.html">About</a> |
<a href="documentation.html">Documentation</a> |
<a href="examples.html">Examples</a> |
<a href="dh.html">Digital Humanities</a>
</p>
-->
</div>
<div class="container-fluid">
<div class="pb-2 mt-4 mb-2 border-bottom"><!-- Page header -->
<h1>TEI2Zenodo</h1>
<p>A generic webservice to to quickly push <a href="https://tei-c.org/guidelines/p5/">TEI XML</a> files
to <a href="https://about.zenodo.org/">zenodo</a> deposits, thereby assigning them a
<a href="https://www.doi.org/">DOI identifier</a> and committing them to long-term archival.
It offers a <a href="https://github.com/">github</a> integration, listening to
<a href="https://developer.github.com/webhooks/">webhooks</a> sent by github and updating the TEI files
both on zenodo and in the git repository with the new zenodo DOI identifiers.</p>
</div>
<div class="main">
<div class="pb-2 mt-4 mb-2" id="quickstart">
<h2>Quickstart</h2>