├── .gitignore ├── sample_nic_barcode.png ├── go.mod ├── main.go ├── nic_validator.go ├── README.md ├── go.sum └── nic_generator.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | sri-lanka-nic-generator-validator -------------------------------------------------------------------------------- /sample_nic_barcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/randikabanura/sri_lanka_nic_generator_validator/HEAD/sample_nic_barcode.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module randikabanura/sri-lanka-nic-generator-validator 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/Pallinder/go-randomdata v1.2.0 7 | github.com/boombuler/barcode v1.0.1 // indirect 8 | github.com/fatih/color v1.10.0 // indirect 9 | github.com/fsnotify/fsnotify v1.4.9 // indirect 10 | github.com/gin-gonic/gin v1.7.1 11 | github.com/githubnemo/CompileDaemon v1.2.1 // indirect 12 | github.com/hako/durafmt v0.0.0-20210316092057-3a2c319c1acd 13 | ) 14 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | ) 7 | 8 | func main() { 9 | r := gin.Default() 10 | 11 | r.GET("/", ping) 12 | v1 := r.Group("/v1") 13 | { 14 | v1.GET("/", ping) 15 | v1.GET("/ping", ping) 16 | v1.GET("/generator", generator) 17 | v1.GET("/validator", validator) 18 | } 19 | 20 | r.Run(":3000") 21 | } 22 | 23 | func ping(c *gin.Context) { 24 | c.JSON(http.StatusOK, gin.H{ 25 | "message": "pong", 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /nic_validator.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gin-gonic/gin" 6 | "github.com/hako/durafmt" 7 | "net/http" 8 | "strconv" 9 | "time" 10 | ) 11 | 12 | func validator(c *gin.Context) { 13 | layout := "2006-01-02" 14 | dns := c.Query("nic") // Data NIC string 15 | val18 := c.Query("val18") 16 | 17 | if val18 != "false" && val18 != "0" { 18 | val18 = "true" 19 | } 20 | 21 | if len(dns) == 0 { 22 | sendErrorJsonValidator(c, fmt.Errorf("nic parameter does not exist."), http.StatusBadRequest) 23 | return 24 | } 25 | 26 | date, doy, age, err := dateHandler(dns) 27 | 28 | if err != nil { 29 | sendErrorJsonValidator(c, err, http.StatusBadRequest) 30 | return 31 | } 32 | 33 | version, err := versionCheck(dns) 34 | 35 | if err != nil { 36 | sendErrorJsonValidator(c, err, http.StatusBadRequest) 37 | return 38 | } 39 | 40 | sex, err := sexCheck(dns) 41 | 42 | if err != nil { 43 | sendErrorJsonValidator(c, err, http.StatusBadRequest) 44 | return 45 | } 46 | 47 | fage, err := durafmt.ParseString(age.String()) 48 | 49 | if err != nil { 50 | sendErrorJsonValidator(c, err, http.StatusBadRequest) 51 | return 52 | } 53 | 54 | sn, err := serialNumberHandler(dns) 55 | 56 | if err != nil { 57 | sendErrorJsonValidator(c, err, http.StatusBadRequest) 58 | return 59 | } 60 | 61 | cd, err := checkDigitHandler(dns) 62 | 63 | if err != nil { 64 | sendErrorJsonValidator(c, err, http.StatusBadRequest) 65 | return 66 | } 67 | 68 | c.JSON(http.StatusOK, gin.H{ 69 | "status": true, 70 | "date": date.Format(layout), 71 | "doy": doy, 72 | "age": fage.LimitFirstN(3).String(), 73 | "version": version, 74 | "sex": sex, 75 | "sn": gin.H{ 76 | "old": fmt.Sprintf("%03d", sn), 77 | "new": fmt.Sprintf("%04d", sn), 78 | }, 79 | "cd": cd, 80 | "validateStatus": true, 81 | }) 82 | 83 | } 84 | 85 | func checkDigitHandler(dns string) (int, error) { 86 | if len(dns) == 10 { 87 | cd, errCheckDigitParse := strconv.ParseInt(string(dns[8]), 0, 64) // Check digit 88 | 89 | if errCheckDigitParse != nil { 90 | return 0, fmt.Errorf("Error occured in check digit parse.") 91 | } 92 | 93 | return int(cd), nil 94 | } else if len(dns) == 12 { 95 | cd, errCheckDigitParse := strconv.ParseInt(string(dns[len(dns)-1]), 0, 64) // Check digit 96 | 97 | if errCheckDigitParse != nil { 98 | return 0, fmt.Errorf("Error occured in check digit parse.") 99 | } 100 | 101 | return int(cd), nil 102 | } else { 103 | return 0, fmt.Errorf("Error occured on check digit handler.") 104 | } 105 | } 106 | 107 | func serialNumberHandler(dns string) (int, error) { 108 | if len(dns) == 10 { 109 | sn, errSerialNumberParse := strconv.ParseInt(dns[5:8], 10, 64) // Serial Number 110 | 111 | if errSerialNumberParse != nil { 112 | return 0, fmt.Errorf("Error occured in serial number parse.") 113 | } 114 | 115 | return int(sn), nil 116 | } else if len(dns) == 12 { 117 | sn, errSerialNumberParse := strconv.ParseInt(dns[7:11], 10, 64) // Serial Number 118 | 119 | if errSerialNumberParse != nil { 120 | return 0, fmt.Errorf("Error occured in serial number parse.") 121 | } 122 | 123 | return int(sn), nil 124 | } else { 125 | return 0, fmt.Errorf("Error occured on serial number handler.") 126 | } 127 | } 128 | 129 | func sexCheck(dns string) (string, error) { 130 | if len(dns) == 10 { 131 | sas := "Male" 132 | 133 | sosd, errSexParse := strconv.ParseInt(dns[2:5], 0, 64) // String of sex digits 134 | if errSexParse != nil { 135 | return "", fmt.Errorf("Error occured in day of the year parse.") 136 | } 137 | 138 | if sosd > 500 { 139 | sas = "Female" 140 | } 141 | 142 | return sas, nil 143 | } else if len(dns) == 12 { 144 | sas := "Male" 145 | 146 | sosd, errSexParse := strconv.ParseInt(dns[4:7], 0, 64) // String of sex digits 147 | if errSexParse != nil { 148 | return "", fmt.Errorf("Error occured in day of the year parse.") 149 | } 150 | 151 | if sosd > 500 { 152 | sas = "Female" 153 | } 154 | 155 | return sas, nil 156 | } else { 157 | return "", fmt.Errorf("Error occured on sex check.") 158 | } 159 | } 160 | 161 | func versionCheck(dns string) (string, error) { 162 | if len(dns) == 10 { 163 | return "Old", nil 164 | } else if len(dns) == 12 { 165 | return "New", nil 166 | } else { 167 | return "", fmt.Errorf("Error occured on version check.") 168 | } 169 | } 170 | 171 | func dateHandler(dns string) (time.Time, int, time.Duration, error) { 172 | layout := "2006-01-02" 173 | 174 | if len(dns) == 10 { 175 | year := fmt.Sprintf("19%v", dns[0:2]) 176 | fdoy := fmt.Sprintf("%v-01-01", year) 177 | date, errTimeParse := time.Parse(layout, fdoy) 178 | if errTimeParse != nil { 179 | return time.Now(), 0, 0, fmt.Errorf("Error occured in year parse.") 180 | } 181 | 182 | doy, errIntParse := strconv.ParseInt(dns[2:5], 0, 64) 183 | if errIntParse != nil { 184 | return time.Now(), 0, 0, fmt.Errorf("Error occured in day of the year parse.") 185 | } 186 | 187 | if doy > 500 { 188 | doy -= 500 189 | } 190 | date = date.AddDate(0, 0, int(doy-1)) 191 | age := time.Since(date) 192 | 193 | return date, int(doy), age, nil 194 | } else if len(dns) == 12 { 195 | year := dns[0:4] 196 | fdoy := fmt.Sprintf("%v-01-01", year) 197 | date, errTimeParse := time.Parse(layout, fdoy) 198 | if errTimeParse != nil { 199 | return time.Now(), 0, 0, fmt.Errorf("Error occured in year parse.") 200 | } 201 | 202 | doy, errIntParse := strconv.ParseInt(dns[4:7], 0, 64) 203 | if errIntParse != nil { 204 | return time.Now(), 0, 0, fmt.Errorf("Error occured in day of the year parse.") 205 | } 206 | 207 | if doy > 500 { 208 | doy -= 500 209 | } 210 | date = date.AddDate(0, 0, int(doy-1)) 211 | age := time.Since(date) 212 | 213 | return date, int(doy), age, nil 214 | } else { 215 | return time.Now(), 0, 0, fmt.Errorf("nic parameter value is incorrect.") 216 | } 217 | } 218 | 219 | //func validate18Years(val18 string) { 220 | // //bval18 := true 221 | // //layout := "2006-01-02" 222 | // // 223 | // //if val18 == "false" || val18 == "0" { 224 | // // bval18 = false 225 | // //} 226 | //} 227 | 228 | func sendErrorJsonValidator(c *gin.Context, err error, code int) { 229 | c.JSON(code, gin.H{ 230 | "status": false, 231 | "error": err.Error(), 232 | "code": http.StatusText(code), 233 | "validateStatus": false, 234 | }) 235 | } 236 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sri Lanka NIC Generator and Validator 2 | 3 | This is a project to implement a Generator and Validator for Sri Lanka National Identity Card (NIC). 4 | This is currently an API only project build using a state-of-the-art programming language of [Go](https://golang.org/). 5 | This project also utilize Few Go packages including: 6 | * [Gin Web Framework](https://github.com/gin-gonic/) - For API implementation 7 | * [durafmt](https://github.com/hako/durafmt) - For getting duration between Times 8 | * [go-randomdata](https://github.com/Pallinder/go-randomdata) - For getting random Int, Boolean, Times 9 | * [barcode](https://github.com/boombuler/barcode) - For generating the PDF417 barcode that in the NIC version introduced in 2016 10 | 11 | This is currently ongoing project and hope to improve the functionality lot more. 12 | 13 | ## Usage 14 | 15 | ### NIC generator 16 | 17 | For generation of and NIC call ```/v1/generator``` endpoint. It has a few query params it could take 18 | to generate NIC number according to the parameters. These params includes the following: 19 | * sex - f, female, m , male 20 | * date - date of which NIC holder is born (Ex: 1995-05-17) 21 | 22 | #### Provinces 23 | 24 | API is returning a province which includes a number and a name. This province name or number does not 25 | relate to NIC number itself. This is given because Old NIC type had a number indicating the province in the 26 | physical NIC. List of the provinces, and the numbers as follows: 27 | 28 | 1. Western Province 29 | 2. Central Province 30 | 3. Southern Province 31 | 4. Northern Province 32 | 5. Eastern Province 33 | 6. North Western Province 34 | 7. North Central Province 35 | 8. Uva Province 36 | 9. Sabaragahmuwa Province 37 | 38 | #### Barcodes 39 | 40 | New NIC card has a barcode which contain the same data that written in the card itself. 41 | This barcode is type of PDF417 and can be scanned and get the data using simple barcode scanning app. 42 | I have implemented the generation of barcode as a base64 image that can be transferred in an API call. 43 | Generator end point response contain the barcode object for the content nad image of the generated data. 44 | Sample barcode image and data contain in that given below. 45 | 46 | ``` 47 | 00 48 | 196165906402 49 | 08/06/1961 50 | Female 51 | 1972-05-29 52 | 00BFT-710 53 | Ava Martinez 54 | 92 Madison Pkwy, 55 | Northleach, NC, 35229 56 | Ransom Canyon 57 | 485406C0548FDD8FDDF300F312EE947D# 58 | ``` 59 | ![img.png](sample_nic_barcode.png) 60 | 61 | #### Examples 62 | 63 | ``` 64 | // http://localhost:3000/v1/generator?sex=male 65 | // Usage of the sex param 66 | 67 | { 68 | "cd": 4, 69 | "date": "1948-12-23", 70 | "doy": 358, 71 | "nnic": "19483580944", 72 | "onic": "48358944V", 73 | "province": { 74 | "name": "Western Province", 75 | "number": 1 76 | }, 77 | "sex": "Male", 78 | "sn": { 79 | "new": "0094", 80 | "old": "094" 81 | }, 82 | "status": true, 83 | "barcode": { 84 | "content": "00\n199427605649\n03/10/1994\nMale\n2005-03-20.........", 85 | "image": "data:image/png;base64,iVBORw0...." 86 | } 87 | } 88 | 89 | // http://localhost:3000/v1/generator?date=1995-05-17 90 | // Usage of the date param 91 | 92 | { 93 | "cd": 4, 94 | "date": "1995-05-17", 95 | "doy": 137, 96 | "nnic": "199563701074", 97 | "onic": "956371074V", 98 | "province": { 99 | "name": "Southern Province", 100 | "number": 3 101 | }, 102 | "sex": "Female", 103 | "sn": { 104 | "new": "0107", 105 | "old": "107" 106 | }, 107 | "status": true, 108 | "barcode": { 109 | "content": "00\n199427605649\n03/10/1994\nMale\n2005-03-20.........", 110 | "image": "data:image/png;base64,iVBORw0...." 111 | } 112 | } 113 | 114 | // http://localhost:3000/v1/generator?date=1996-06-03&sex=m 115 | // Usage of the both sex and date param 116 | 117 | { 118 | "cd": 6, 119 | "date": "1996-06-03", 120 | "doy": 155, 121 | "nnic": "199615506166", 122 | "onic": "961556166V", 123 | "province": { 124 | "name": "Western Province", 125 | "number": 1 126 | }, 127 | "sex": "Male", 128 | "sn": { 129 | "new": "0616", 130 | "old": "616" 131 | }, 132 | "status": true, 133 | "barcode": { 134 | "content": "00\n199427605649\n03/10/1994\nMale\n2005-03-20.........", 135 | "image": "data:image/png;base64,iVBORw0...." 136 | } 137 | } 138 | 139 | // http://localhost:3000/v1/generator?date=2005-05-17 140 | // Usage of if birthday is in or after year 2000 141 | // Note that if the birthday is in or after year 2000, 142 | // response will not have onic (Old NIC) and Old serial number 143 | 144 | { 145 | "cd": 4, 146 | "date": "2005-05-17", 147 | "doy": 137, 148 | "nnic": "200563797534", 149 | "onic": "", 150 | "sex": "Female", 151 | "sn": { 152 | "new": "9753", 153 | "old": "" 154 | }, 155 | "status": true, 156 | "barcode": { 157 | "content": "00\n199427605649\n03/10/1994\nMale\n2005-03-20.........", 158 | "image": "data:image/png;base64,iVBORw0...." 159 | } 160 | } 161 | ``` 162 | 163 | ### NIC validator 164 | For validation of the NIC call ```/v1/validator``` endpoint. It must have a ```nic``` parameter. 165 | Otherwise it will return an error saying that ```nic``` parameter is empty. NIC that send in the parameter ```nic``` could be 166 | both new of old version of the NIC in sri lanka 167 | 168 | #### Examples 169 | 170 | ``` 171 | // http://localhost:3000/v1/validator?nic=956380995V 172 | // Using old version of the NIC 173 | 174 | { 175 | "age": "25 years 47 weeks 6 days", 176 | "cd": 5, 177 | "date": "1995-05-18", 178 | "doy": 138, 179 | "sex": "Female", 180 | "status": true, 181 | "sn": { 182 | "new": "0099", 183 | "old": "099" 184 | }, 185 | "validateStatus": true, 186 | "version": "Old" 187 | } 188 | 189 | // http://localhost:3000/v1/validator?nic=199615500343 190 | // Using new version of the NIC 191 | 192 | { 193 | "age": "24 years 45 weeks 3 days", 194 | "cd": 3, 195 | "date": "1996-06-03", 196 | "doy": 155, 197 | "sex": "Male", 198 | "sn": { 199 | "new": "0034", 200 | "old": "034" 201 | }, 202 | "status": true, 203 | "validateStatus": true, 204 | "version": "New" 205 | } 206 | 207 | // http://localhost:3000/v1/validator?nic=9563809s95V 208 | // Response when NIC is not valid 209 | 210 | { 211 | "code": "Bad Request", 212 | "error": "nic parameter value is incorrect.", 213 | "status": false, 214 | "validateStatus": false 215 | } 216 | 217 | ``` 218 | 219 | ## Do you like it? Star it! 220 | If you use this component just star it. A developer is more motivated to improve a project when there is some interest. 221 | 222 | ## Contributing 223 | Bug reports and pull requests are welcome on GitHub at https://github.com/randikabanura/sri-lanka-nic-generator-validator. 224 | 225 | ## License 226 | The software is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 227 | 228 | ## Developer 229 | Name: [Banura Randika Perera](https://github.com/randikabanura)
230 | Linkedin: [randika-banura](https://www.linkedin.com/in/randika-banura/)
231 | Email: [randika.banura@gamil.com](mailto:randika.banura@gamil.com)
232 | Bsc (Hons) Information Technology specialized in Software Engineering (SLIIT) -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Pallinder/go-randomdata v1.2.0 h1:DZ41wBchNRb/0GfsePLiSwb0PHZmT67XY00lCDlaYPg= 2 | github.com/Pallinder/go-randomdata v1.2.0/go.mod h1:yHmJgulpD2Nfrm0cR9tI/+oAgRqCQQixsA8HyRZfV9Y= 3 | github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs= 4 | github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= 5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= 9 | github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= 10 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 11 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 12 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 13 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 14 | github.com/gin-gonic/gin v1.7.1 h1:qC89GU3p8TvKWMAVhEpmpB2CIb1hnqt2UdKZaP93mS8= 15 | github.com/gin-gonic/gin v1.7.1/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= 16 | github.com/githubnemo/CompileDaemon v1.2.1 h1:yEJ28U3sIsuhCtpqN2Pb5XL8G+hpg7KUNfTGvL7GxL0= 17 | github.com/githubnemo/CompileDaemon v1.2.1/go.mod h1:lE3EXX1td33uhlkFLp+ImWY9qBaoRcDeA3neh4m8ic0= 18 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= 19 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 20 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= 21 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= 22 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= 23 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 24 | github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= 25 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= 26 | github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= 27 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 28 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 29 | github.com/hako/durafmt v0.0.0-20210316092057-3a2c319c1acd h1:FsX+T6wA8spPe4c1K9vi7T0LvNCO1TTqiL8u7Wok2hw= 30 | github.com/hako/durafmt v0.0.0-20210316092057-3a2c319c1acd/go.mod h1:VzxiSdG6j1pi7rwGm/xYI5RbtpBgM8sARDXlvEvxlu0= 31 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= 32 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 33 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 34 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 35 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 36 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= 37 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= 38 | github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= 39 | github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 40 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 41 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 42 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= 43 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 44 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= 45 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 46 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= 47 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 48 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 49 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 50 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 51 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 52 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 53 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 54 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= 55 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 56 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= 57 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 58 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 59 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= 60 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 61 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 62 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 63 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 64 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 65 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= 66 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 67 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= 68 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 69 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 70 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 71 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 72 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 73 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 74 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= 75 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 76 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 77 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 78 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 79 | -------------------------------------------------------------------------------- /nic_generator.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "github.com/Pallinder/go-randomdata" 7 | "github.com/boombuler/barcode/pdf417" 8 | "github.com/gin-gonic/gin" 9 | "image/png" 10 | "io/ioutil" 11 | "math" 12 | "net/http" 13 | "os" 14 | "strings" 15 | "time" 16 | ) 17 | 18 | func generator(c *gin.Context) { 19 | layout := "2006-01-02" 20 | dqs := c.Query("date") 21 | provinces := []string{"Western", "Central", "Southern", "Northern", "Eastern", "North Western", "North Central", "Uva", "Sabaragahmuwa"} 22 | 23 | date, err := dateQueryHandler(dqs) // Date query string 24 | 25 | if err != nil { 26 | sendErrorJsonGenerator(c, err, http.StatusBadRequest) 27 | return 28 | } 29 | 30 | sqs := c.Query("sex") // Sex query string 31 | sex, sas, err := sexQueryHandler(sqs) 32 | 33 | if err != nil { 34 | sendErrorJsonGenerator(c, err, http.StatusBadRequest) 35 | return 36 | } 37 | 38 | fdoy := time.Date(date.Year(), 1, 1, 0, 0, 0, 0, time.UTC) // First day of the year 39 | doy := int(math.Ceil(date.Sub(fdoy).Hours()/24) + 1) // Day of the year 40 | 41 | sn := randomdata.Number(0, 1000) // Serial number 42 | if date.Year() >= 2000 { 43 | sn = randomdata.Number(0, 10000) 44 | } 45 | 46 | cd := randomdata.Number(0, 10) // Check digit 47 | 48 | sdoy := doy // Day of the year according to sex 49 | if sex == false { 50 | sdoy += 500 51 | } 52 | 53 | onic := generateONIC(date.Year(), sdoy, sn, cd) 54 | osn := fmt.Sprintf("%03d", sn) // Old serial number 55 | if len(onic) != 10 { 56 | onic = "" 57 | osn = "" 58 | } 59 | 60 | nnic := generateNNIC(date.Year(), sdoy, sn, cd) 61 | nsn := fmt.Sprintf("%04d", sn) // New serial number 62 | barcode := "" 63 | barcodeContent := "" 64 | if len(nnic) != 12 { 65 | nnic = "" 66 | nsn = "" 67 | } else { 68 | barcodeContent, barcode, err = generateBarcodeForNNIC(nnic, date, sas) 69 | if err != nil { 70 | sendErrorJsonGenerator(c, err, http.StatusBadRequest) 71 | return 72 | } 73 | } 74 | 75 | pn := randomdata.Number(0, 9) // Province number. This is not associated with the NIC number 76 | ps := fmt.Sprintf("%v Province", provinces[pn]) // String of province 77 | 78 | c.JSON(http.StatusOK, gin.H{ 79 | "status": true, 80 | "date": date.Format(layout), 81 | "doy": doy, 82 | "sn": gin.H{ 83 | "old": osn, // Old serial number 84 | "new": nsn, // New serial number 85 | }, 86 | "cd": cd, 87 | "sex": sas, 88 | "onic": onic, // Old nic version 89 | "nnic": nnic, // New nic version 90 | "province": gin.H{ // Province is not associated with the NIC number 91 | "number": pn + 1, 92 | "name": ps, 93 | }, 94 | "barcode": gin.H{ 95 | "content": barcodeContent, 96 | "image": barcode, 97 | }, 98 | }) 99 | } 100 | 101 | // Handle error response if any error occurred for generator 102 | func sendErrorJsonGenerator(c *gin.Context, err error, code int) { 103 | c.JSON(code, gin.H{ 104 | "status": false, 105 | "error": err.Error(), 106 | "code": http.StatusText(code), 107 | }) 108 | } 109 | 110 | // Handles a date query param. If not available it auto generate random date 111 | func dateQueryHandler(dqs string) (time.Time, error) { 112 | layout := "2006-01-02" 113 | date := time.Now() 114 | var err error = nil 115 | 116 | if len(dqs) > 0 { 117 | date, err = time.Parse(layout, dqs) 118 | } else { 119 | db18 := time.Now().AddDate(-18, 0, 0).Format(layout) // Date 18 years before today 120 | db118 := time.Now().AddDate(-118, 0, 0).Format(layout) // Date 118 years before today 121 | date, err = time.Parse("Monday 2 Jan 2006", randomdata.FullDateInRange(db118, db18)) 122 | } 123 | 124 | return date, err 125 | } 126 | 127 | // Handles the sex query param and return a boolean, string and error 128 | func sexQueryHandler(sqs string) (bool, string, error) { 129 | sqs = strings.ToLower(sqs) 130 | 131 | switch sqs { 132 | case "m", "male": 133 | { 134 | return true, "Male", nil 135 | } 136 | case "f", "female": 137 | { 138 | return false, "Female", nil 139 | } 140 | case "": 141 | { 142 | rs := randomdata.Boolean() // Random sex boolean 143 | rss := "Male" // initialize the the sex string 144 | 145 | // Change the sex string if rs is false 146 | if rs == false { 147 | rss = "Female" 148 | } 149 | return rs, rss, nil 150 | } 151 | default: 152 | return false, "", fmt.Errorf("Sex parameter can not be parsed.") 153 | } 154 | } 155 | 156 | // Generate old nic version according to year, day of the year, serial number and check digit 157 | func generateONIC(year int, doy int, sn int, cd int) string { 158 | if sn > 999 || year > 2000 { 159 | return "" 160 | } 161 | 162 | sy := year % 100 163 | ssy := fmt.Sprintf("%v", sy) 164 | 165 | if sy < 10 { 166 | ssy = fmt.Sprintf("0%v", sy) 167 | } 168 | 169 | return fmt.Sprintf("%v%03d%03d%d%v", ssy, doy, sn, cd, "V") 170 | } 171 | 172 | // Generate new nic version according to year, day of the year, serial number and check digit 173 | func generateNNIC(year int, doy int, sn int, cd int) string { 174 | return fmt.Sprintf("%d%03d%04d%d", year, doy, sn, cd) 175 | } 176 | 177 | // Generate the pdf417 barcode for the new NIC number 178 | func generateBarcodeForNNIC(nnic string, date time.Time, sas string) (string, string, error) { 179 | layoutOne := "2006-01-02" 180 | layoutTwo := "02/01/2006" 181 | fullName := "" 182 | if sas == "Male" { 183 | fullName = randomdata.FullName(randomdata.Male) 184 | } else { 185 | fullName = randomdata.FullName(randomdata.Female) 186 | } 187 | 188 | db := date.Format(layoutOne) 189 | db16 := date.AddDate(16, 0, 0).Format(layoutOne) // Date 16 years after today 190 | createdDate, err := time.Parse("Monday 2 Jan 2006", randomdata.FullDateInRange(db, db16)) 191 | if err != nil { 192 | return "", "", err 193 | } 194 | 195 | barcodeStr := "00\n" + 196 | nnic + "\n" + 197 | date.Format(layoutTwo) + "\n" + 198 | sas + "\n" + 199 | createdDate.Format(layoutOne) + "\n" + 200 | "00BFT-710\n" + 201 | fullName + "\n" + 202 | randomdata.Address() + "\n" + 203 | randomdata.City() + "\n" + 204 | "485406C0548FDD8FDDF300F312EE947D#" 205 | 206 | currentTime := time.Now().Unix() 207 | 208 | pdf417Code, err := pdf417.Encode(barcodeStr, 0) 209 | if err != nil { 210 | return "", "", err 211 | } 212 | 213 | // create the output file 214 | file, err := os.Create(fmt.Sprintf("barcode-%v.png", currentTime)) 215 | if err != nil { 216 | return "", "", err 217 | } 218 | 219 | defer file.Close() 220 | 221 | err = png.Encode(file, pdf417Code) 222 | if err != nil { 223 | return "", "", err 224 | } 225 | 226 | bytes, err := ioutil.ReadFile(fmt.Sprintf("./barcode-%v.png", currentTime)) 227 | if err != nil { 228 | return "", "", err 229 | } 230 | 231 | err = os.Remove(fmt.Sprintf("barcode-%v.png", currentTime)) 232 | if err != nil { 233 | return "", "", err 234 | } 235 | 236 | mimeType := http.DetectContentType(bytes) 237 | 238 | // Prepend the appropriate URI scheme header depending 239 | // on the MIME type 240 | base64Encoding := getMimeType(mimeType) 241 | 242 | // Append the base64 encoded output 243 | base64Encoding += toBase64(bytes) 244 | 245 | // Print the full base64 representation of the image 246 | return pdf417Code.Content(), base64Encoding, nil 247 | } 248 | 249 | func toBase64(b []byte) string { 250 | return base64.StdEncoding.EncodeToString(b) 251 | } 252 | 253 | func getMimeType(mimetype string) string { 254 | base64Encoding := "" 255 | 256 | switch mimetype { 257 | case "image/jpeg": 258 | base64Encoding = "data:image/jpeg;base64," 259 | case "image/png": 260 | base64Encoding = "data:image/png;base64," 261 | } 262 | 263 | return base64Encoding 264 | } 265 | --------------------------------------------------------------------------------